Skip to content

Commit 8f25dde

Browse files
fix(tui-scrollview): make scroll_view buffer area the same as its content (#37)
This makes the scroll_view buffer area the same as its content by default. The previous behavior is easily reproduced by adding empty space to the buffer. =) Most tests remain the same with the scrollbar updated to match the new size. Fixes: #35
1 parent 18e3c03 commit 8f25dde

File tree

1 file changed

+83
-37
lines changed

1 file changed

+83
-37
lines changed

tui-scrollview/src/scroll_view.rs

+83-37
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,19 @@ impl StatefulWidget for ScrollView {
118118
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
119119
let (mut x, mut y) = state.offset.into();
120120
// ensure that we don't scroll past the end of the buffer in either direction
121-
x = x.min(self.buf.area.width.saturating_sub(1));
122-
y = y.min(self.buf.area.height.saturating_sub(1));
121+
let max_x_offset = self
122+
.buf
123+
.area
124+
.width
125+
.saturating_sub(area.width.saturating_sub(1));
126+
let max_y_offset = self
127+
.buf
128+
.area
129+
.height
130+
.saturating_sub(area.height.saturating_sub(1));
131+
132+
x = x.min(max_x_offset);
133+
y = y.min(max_y_offset);
123134
state.offset = (x, y).into();
124135
state.size = Some(self.size);
125136
state.page_size = Some(area.into());
@@ -135,6 +146,7 @@ impl ScrollView {
135146
/// scrollbars
136147
fn render_scrollbars(&self, area: Rect, buf: &mut Buffer, state: &mut ScrollViewState) -> Rect {
137148
let size = self.size;
149+
138150
let width = size.width.saturating_sub(area.width);
139151
let height = size.height.saturating_sub(area.height);
140152
match (width, height) {
@@ -147,46 +159,48 @@ impl ScrollView {
147159
// area is taller and narrower than the scroll_view
148160
state.offset.y = 0;
149161
self.render_horizontal_scrollbar(area, buf, state);
150-
Rect::new(state.offset.x, 0, area.width, area.height - 1)
162+
Rect::new(state.offset.x, 0, area.width, area.height.saturating_sub(1))
151163
}
152164
(0, _) if area.width > size.width => {
153165
// area is wider and shorter than the scroll_view
154166
state.offset.x = 0;
155167
self.render_vertical_scrollbar(area, buf, state);
156-
Rect::new(0, state.offset.y, area.width - 1, area.height)
168+
Rect::new(0, state.offset.y, area.width.saturating_sub(1), area.height)
157169
}
158170
(_, _) => {
159171
// scroll_view is both wider and taller than the area
160172
let vertical_area = Rect {
161-
height: area.height - 1,
173+
height: area.height.saturating_sub(1),
162174
..area
163175
};
164176
let horizontal_area = Rect {
165-
width: area.width - 1,
177+
width: area.width.saturating_sub(1),
166178
..area
167179
};
168180
self.render_vertical_scrollbar(vertical_area, buf, state);
169181
self.render_horizontal_scrollbar(horizontal_area, buf, state);
170182
Rect::new(
171183
state.offset.x,
172184
state.offset.y,
173-
area.width - 1,
174-
area.height - 1,
185+
area.width.saturating_sub(1),
186+
area.height.saturating_sub(1),
175187
)
176188
}
177189
}
178190
}
179191

180192
fn render_vertical_scrollbar(&self, area: Rect, buf: &mut Buffer, state: &ScrollViewState) {
193+
let scrollbar_height = self.size.height.saturating_sub(area.height);
181194
let mut scrollbar_state =
182-
ScrollbarState::new(self.size.height as usize).position(state.offset.y as usize);
195+
ScrollbarState::new(scrollbar_height as usize).position(state.offset.y as usize);
183196
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight);
184197
scrollbar.render(area, buf, &mut scrollbar_state);
185198
}
186199

187200
fn render_horizontal_scrollbar(&self, area: Rect, buf: &mut Buffer, state: &ScrollViewState) {
201+
let scrollbar_width = self.size.width.saturating_sub(area.width);
188202
let mut scrollbar_state =
189-
ScrollbarState::new(self.size.width as usize).position(state.offset.x as usize);
203+
ScrollbarState::new(scrollbar_width as usize).position(state.offset.x as usize);
190204
let scrollbar = Scrollbar::new(ScrollbarOrientation::HorizontalBottom);
191205
scrollbar.render(area, buf, &mut scrollbar_state);
192206
}
@@ -246,10 +260,10 @@ mod tests {
246260
Buffer::with_lines(vec![
247261
"ABCDE▲",
248262
"KLMNO█",
249-
"UVWXY",
263+
"UVWXY",
250264
"EFGHI║",
251265
"OPQRS▼",
252-
"◄█═► ",
266+
"◄█═► ",
253267
])
254268
)
255269
}
@@ -264,10 +278,10 @@ mod tests {
264278
Buffer::with_lines(vec![
265279
"DEFGH▲",
266280
"NOPQR█",
267-
"XYZAB",
281+
"XYZAB",
268282
"HIJKL║",
269283
"RSTUV▼",
270-
"◄═█► ",
284+
"◄═█► ",
271285
])
272286
)
273287
}
@@ -283,9 +297,9 @@ mod tests {
283297
"EFGHI▲",
284298
"OPQRS║",
285299
"YZABC█",
286-
"IJKLM",
300+
"IJKLM",
287301
"STUVW▼",
288-
"◄█═► ",
302+
"◄█═► ",
289303
])
290304
)
291305
}
@@ -325,9 +339,9 @@ mod tests {
325339
"UVWXYZABCD█",
326340
"EFGHIJKLMN█",
327341
"OPQRSTUVWX█",
328-
"YZABCDEFGH",
329-
"IJKLMNOPQR",
330-
"STUVWXYZAB",
342+
"YZABCDEFGH",
343+
"IJKLMNOPQR",
344+
"STUVWXYZAB",
331345
"CDEFGHIJKL▼",
332346
])
333347
)
@@ -351,7 +365,7 @@ mod tests {
351365
"STUVWXYZA",
352366
"CDEFGHIJK",
353367
"MNOPQRSTU",
354-
"◄████═══►",
368+
"◄███████►",
355369
])
356370
)
357371
}
@@ -370,11 +384,11 @@ mod tests {
370384
"KLMNOPQRS█",
371385
"UVWXYZABC█",
372386
"EFGHIJKLM█",
373-
"OPQRSTUVW",
374-
"YZABCDEFG",
387+
"OPQRSTUVW",
388+
"YZABCDEFG",
375389
"IJKLMNOPQ║",
376390
"STUVWXYZA▼",
377-
"◄████═══► ",
391+
"◄███████► ",
378392
])
379393
)
380394
}
@@ -394,21 +408,21 @@ mod tests {
394408
"UVWXYZAB█",
395409
"EFGHIJKL█",
396410
"OPQRSTUV█",
397-
"YZABCDEF",
398-
"IJKLMNOP",
399-
"STUVWXYZ",
411+
"YZABCDEF",
412+
"IJKLMNOP",
413+
"STUVWXYZ",
400414
"CDEFGHIJ▼",
401-
"◄███═══► ",
415+
"◄█████═► ",
402416
])
403417
)
404418
}
405419

406420
/// The purpose of this test is to ensure that the buffer offset is correctly calculated when
407-
/// rendering a scroll view into a buffer (i.e. the buffer offset is not always (0, 0))
421+
/// rendering a scroll view into a buffer (i.e. the buffer offset is not always (0, 0)).
408422
#[rstest]
409423
fn ensure_buffer_offset_is_correct(scroll_view: ScrollView) {
410424
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 20));
411-
let mut state = ScrollViewState::with_offset((3, 4).into());
425+
let mut state = ScrollViewState::with_offset((2, 3).into());
412426
scroll_view.render(Rect::new(5, 6, 7, 8), &mut buf, &mut state);
413427
assert_eq!(
414428
buf,
@@ -419,14 +433,14 @@ mod tests {
419433
" ",
420434
" ",
421435
" ",
422-
" RSTUVW▲ ",
423-
" BCDEFG║ ",
424-
" LMNOPQ█ ",
425-
" VWXYZA█ ",
426-
" FGHIJK║ ",
427-
" PQRSTU║ ",
428-
" ▼ ",
429-
" ◄═█══► ",
436+
" GHIJKL▲ ",
437+
" QRSTUV║ ",
438+
" ABCDEF█ ",
439+
" KLMNOP█ ",
440+
" UVWXYZ█ ",
441+
" EFGHIJ█ ",
442+
" OPQRST▼ ",
443+
" ◄═███► ",
430444
" ",
431445
" ",
432446
" ",
@@ -436,4 +450,36 @@ mod tests {
436450
])
437451
)
438452
}
453+
/// The purpose of this test is to ensure that the last elements are rendered.
454+
#[rstest]
455+
fn ensure_buffer_last_elements(scroll_view: ScrollView) {
456+
let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6));
457+
let mut state = ScrollViewState::with_offset((5, 5).into());
458+
scroll_view.render(buf.area, &mut buf, &mut state);
459+
assert_eq!(
460+
buf,
461+
Buffer::with_lines(vec![
462+
"DEFGH▲",
463+
"NOPQR║",
464+
"XYZAB█",
465+
"HIJKL█",
466+
"RSTUV▼",
467+
"◄═██► ",
468+
])
469+
)
470+
}
471+
#[rstest]
472+
#[should_panic(expected = "Scrollbar area is empty")]
473+
fn zero_width(scroll_view: ScrollView) {
474+
let mut buf = Buffer::empty(Rect::new(0, 0, 0, 10));
475+
let mut state = ScrollViewState::new();
476+
scroll_view.render(buf.area, &mut buf, &mut state);
477+
}
478+
#[rstest]
479+
#[should_panic(expected = "Scrollbar area is empty")]
480+
fn zero_height(scroll_view: ScrollView) {
481+
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 0));
482+
let mut state = ScrollViewState::new();
483+
scroll_view.render(buf.area, &mut buf, &mut state);
484+
}
439485
}

0 commit comments

Comments
 (0)