-
Notifications
You must be signed in to change notification settings - Fork 25
feat: Query if ScrollViewState is_at_bottom() #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
7b0e319
7a62a60
805d875
cbfbe82
2fe46b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -194,7 +194,6 @@ impl ScrollView { | |
| /// | ||
| /// This should not be confused with the `render` method, which renders the visible area of the | ||
| /// ScrollView into the main buffer. | ||
|
|
||
| pub fn render_stateful_widget<W: StatefulWidget>( | ||
| &mut self, | ||
| widget: W, | ||
|
|
@@ -451,6 +450,65 @@ mod tests { | |
| ) | ||
| } | ||
|
|
||
| #[rstest] | ||
| fn move_to_bottom(scroll_view: ScrollView) { | ||
| let mut buf = Buffer::empty(Rect::new(0, 0, 6, 6)); | ||
| let mut state = ScrollViewState::default(); | ||
| scroll_view.clone().render(buf.area, &mut buf, &mut state); | ||
|
|
||
| // The vertical view size is five which means the page size is five. | ||
| // We have not scrolled yet, view is at the top and not the at the bottom. | ||
| // => We see the top five rows | ||
| assert_eq!(state.offset.y, 0); | ||
| assert!(!state.is_at_bottom()); | ||
| assert_eq!( | ||
| buf, | ||
| Buffer::with_lines(vec![ | ||
| "ABCDE▲", | ||
| "KLMNO█", | ||
| "UVWXY█", | ||
| "EFGHI║", | ||
| "OPQRS▼", | ||
| "◄██═► ", | ||
| ]) | ||
| ); | ||
|
|
||
| // Since the content height is ten, | ||
| assert_eq!(state.size.unwrap().height, 10); | ||
| // if we scroll down one page (five rows), | ||
| state.scroll_down(); | ||
| state.scroll_down(); | ||
| state.scroll_down(); | ||
| state.scroll_down(); | ||
| state.scroll_down(); | ||
|
|
||
| // we reach the bottom, | ||
| assert!(state.is_at_bottom()); | ||
| assert_eq!(state.offset.y, 5); | ||
|
|
||
| // and we see the last five rows of the content. | ||
| scroll_view.render(buf.area, &mut buf, &mut state); | ||
| assert_eq!( | ||
| buf, | ||
| Buffer::with_lines(vec![ | ||
| "YZABC▲", | ||
| "IJKLM║", | ||
| "STUVW█", | ||
| "CDEFG█", | ||
| "MNOPQ▼", | ||
| "◄██═► ", | ||
| ]) | ||
| ); | ||
|
|
||
| // We could also jump directly to the bottom | ||
| state.scroll_to_bottom(); | ||
| assert!(state.is_at_bottom()); | ||
|
|
||
| // which sets the offset to the last row of content, | ||
| // ensuring to be at the bottom regardless of the page size. | ||
| assert_eq!(state.offset.y, state.size.unwrap().height - 1); | ||
|
||
| } | ||
|
|
||
| #[rstest] | ||
| fn hides_both_scrollbars(scroll_view: ScrollView) { | ||
| let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10)); | ||
|
|
@@ -787,10 +845,10 @@ mod tests { | |
| let mut buf = Buffer::empty(Rect::new(0, 0, 7, 5)); | ||
| let mut state = ScrollViewState::default(); | ||
| let mut list_state = ListState::default(); | ||
| let items: Vec<String> = (1..=10).map(|i| format!("Item {}", i)).collect(); | ||
| let items: Vec<String> = (1..10).map(|i| format!("Item {}", i)).collect(); | ||
Teufelchen1 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let list = List::new(items); | ||
| scroll_view.render_stateful_widget(list, scroll_view.area(), &mut list_state); | ||
| scroll_view.render(buf.area, &mut buf, &mut state); | ||
| scroll_view.clone().render(buf.area, &mut buf, &mut state); | ||
|
Comment on lines
-793
to
+843
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure why this clone is needed here
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is actually reassuring to see that you also don't get it immediately (makes me feel less like an idiot). I was very confused when Rust hit me with this (when you omit that error[E0382]: use of moved value: `scroll_view`
--> tui-scrollview/src/scroll_view.rs:482:9
|
454 | fn move_to_bottom(scroll_view: ScrollView) {
| ----------- move occurs because `scroll_view` has type `scroll_view::ScrollView`, which does not implement the `Copy` trait
...
461 | scroll_view.render(buf.area, &mut buf, &mut state);
| -------------------------------------- `scroll_view` moved due to this method call
...
482 | scroll_view.render(buf.area, &mut buf, &mut state);
| ^^^^^^^^^^^ value used here after move
|
note: `ratatui::prelude::StatefulWidget::render` takes ownership of the receiver `self`, which moves `scroll_view`
--> /home/teufelchen/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ratatui-0.29.0/src/widgets.rs:244:15
|
244 | fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State);
| ^^^^
help: you can `clone` the value and consume it, but this might not be your desired behavior
|
461 | scroll_view.clone().render(buf.area, &mut buf, &mut state);
| ++++++++
For more information about this error, try `rustc --explain E0382`.
I have to admit I am unable to explain it really. I figured it has something to do with |
||
| assert_eq!( | ||
| buf, | ||
| Buffer::with_lines(vec![ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,4 +82,14 @@ impl ScrollViewState { | |
| .map_or(u16::MAX, |size| size.height.saturating_sub(1)); | ||
| self.offset.y = bottom; | ||
| } | ||
|
|
||
| /// True if the scroll view state is at the bottom of the buffer | ||
| /// Takes the pagesize into account | ||
|
||
| pub fn is_at_bottom(&self) -> bool { | ||
| let bottom = self | ||
| .size | ||
| .map_or(u16::MAX, |size| size.height.saturating_sub(1)); | ||
| let page_size = self.page_size.map_or(0, |size| size.height); | ||
| self.offset.y + page_size >= bottom | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not strictly necessary as that is already covered by other tests but I liked the visual story telling of scrolling down.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tend to prefer not putting these sort of pre-condition assertions that are covered elsewhere in tests. My rationale for this preference is that if some other change fails to render the starting condition, I'd prefer to see this fail at the point where it's testing the end condition, rather than in the pre-conditions. As a general rule, I've found that this makes it easier to debug failing tests while developing as well as to maintain them when things change which would affect pre-conditions but not post-conditions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I am with you on that opinion. Dropped it.