Skip to content
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

feat: add scrollbar widget #228

Merged
merged 1 commit into from
Jun 17, 2023
Merged

feat: add scrollbar widget #228

merged 1 commit into from
Jun 17, 2023

Conversation

kdheepak
Copy link
Collaborator

@kdheepak kdheepak commented Jun 8, 2023

I've added a simple scrollbar widget.

 <--▮------->
 ^  ^   ^   ^
 │  │   │   └ end arrow
 │  │   └──── track symbol
 │  └──────── thumb symbol
 └─────────── begin arrow
image

It can be used to add a scrollbar to any Rect.
It can also be customized with different styles and symbols for, the bar, the "scrollwheel" and arrows.

This addresses #173

A larger discussion is to be had about scrollable widgets in general: #174

I'm open to feedback on improving this. I've needed a scrollbar for a couple of projects now and figured I'd clean up the code and share it as a Widget here.

Docstrings were generated using ChatGPT.

@codecov
Copy link

codecov bot commented Jun 8, 2023

Codecov Report

Merging #228 (c436716) into main (8b7b788) will increase coverage by 0.45%.
The diff coverage is 87.71%.

❗ Current head c436716 differs from pull request most recent head e5f0212. Consider uploading reports for the commit e5f0212 to get more accurate results

@@            Coverage Diff             @@
##             main     #228      +/-   ##
==========================================
+ Coverage   81.92%   82.38%   +0.45%     
==========================================
  Files          34       35       +1     
  Lines        6627     7197     +570     
==========================================
+ Hits         5429     5929     +500     
- Misses       1198     1268      +70     
Impacted Files Coverage Δ
src/widgets/mod.rs 66.66% <ø> (ø)
src/widgets/scrollbar.rs 87.71% <87.71%> (ø)

@joshka joshka mentioned this pull request Jun 8, 2023
Copy link
Member

@joshka joshka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done a bit of experimenting with scrolling logic too, but mainly playing with the state part not the widget part. I just slapped a WIP PR for you to take a look at (#229). Perhaps there's some ideas that you could incorporate into this there?

My main concern here is over having a single position rather than a range. It's probably easy to move to a range if we use an enum to represent the position enum ScrollPosition { Ratio(f64), RatioRange(f64, f64), Range(u16, u16), etc. }

src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
@joshka
Copy link
Member

joshka commented Jun 8, 2023

Thanks for working on this btw - looking forward to seeing it be part of ratatui.

@kdheepak
Copy link
Collaborator Author

kdheepak commented Jun 9, 2023

Thanks for the detailed feedback!!!

Let me address some of the comments and get back to you when it is ready for another round for review.

I'm also planning to update this scrollbar widget so that it works horizontally as well.

src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
Copy link
Member

@joshka joshka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice overall

I'd still prefer to have the position part out of the scroll bar - based on the stuff in the other PR / design Issue, but this is really good. I'll take a full review sometime soon.

@kdheepak
Copy link
Collaborator Author

kdheepak commented Jun 9, 2023

You can ignore this comment, thanks to @joshka this has been resolved. Original comment in the details of this message.

I'm getting a failing test on CI:

[cargo-make] INFO - Execute Command: "cargo" "check" "--no-default-features" "--features" "serde,termion" "--all-targets"
 Downloading crates ...
  Downloaded termion v2.0.1
  Downloaded numtoa v0.1.0
    Checking numtoa v0.1.0
    Checking termion v2.0.1
    Checking ratatui v0.21.0 (/home/runner/work/ratatui/ratatui)
error[E0432]: unresolved import `ratatui::backend::CrosstermBackend`
Error:  --> examples/scrollbar.rs:7:24
  |
7 |     backend::{Backend, CrosstermBackend},
  |                        ^^^^^^^^^^^^^^^^ no `CrosstermBackend` in `backend`

error[E0433]: failed to resolve: use of undeclared crate or module `crossterm`
Error:  --> examples/scrollbar.rs:1:5
  |
1 | use crossterm::{
  |     ^^^^^^^^^ use of undeclared crate or module `crossterm`

error[E0432]: unresolved import `crossterm`
Error:  --> examples/scrollbar.rs:1:5
  |
1 | use crossterm::{
  |     ^^^^^^^^^ use of undeclared crate or module `crossterm`

error: cannot determine resolution for the macro `execute`
Error:   --> examples/scrollbar.rs:30:5
   |
30 |     execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
   |     ^^^^^^^
   |
   = note: import resolution is stuck, try simplifying macro imports

error: cannot determine resolution for the macro `execute`
Error:   --> examples/scrollbar.rs:41:5
   |
41 |     execute!(
   |     ^^^^^^^
   |
   = note: import resolution is stuck, try simplifying macro imports

error[E0433]: failed to resolve: use of undeclared crate or module `crossterm`
Error:   --> examples/scrollbar.rs:67:12
   |
67 |         if crossterm::event::poll(timeout)? {
   |            ^^^^^^^^^ use of undeclared crate or module `crossterm`

Some errors have detailed explanations: E0432, E0433.
For more information about an error, try `rustc --explain E0432`.
error: could not compile `ratatui` (example "scrollbar") due to 6 previous errors
warning: build failed, waiting for other jobs to finish...
[cargo-make] ERROR - Error while executing command, exit code: 101
[cargo-make] WARN - Build Failed.
Error: Process completed with exit code 1.

How is this test passing for the examples/paragraph.rs file? That file also uses crossterm. Is there a way to skip it?

src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
@joshka
Copy link
Member

joshka commented Jun 9, 2023

For the build failure check the cargo toml to see how to configure examples.

@kdheepak
Copy link
Collaborator Author

kdheepak commented Jun 9, 2023

I think this is ready for a final review now!

@sayanarijit
Copy link
Member

Quick glance looks good.

Copy link
Member

@joshka joshka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of comments still

src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
@kdheepak kdheepak requested a review from joshka June 17, 2023 03:09
@kdheepak
Copy link
Collaborator Author

@joshka I've addressed a number of your comments and asked for some clarification on a new questions.

Copy link
Member

@joshka joshka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Picking back up the conversation about the cross. The code doesn't make it clear how the cross (perhaps you mean corner?) is relevant to the the algorithm for rendering the scroll_bar.

If I'm thinking through this from basics, 200 lines to render a scrollbar seems excessive.
It's possible that there are some assumptions or non obvious edge cases that you have factored in here, but they are difficult to see. I'm not sure it would be easy to fix a problem in this code, or to work out how to add a new feature.

I think perhaps this works too much in absolute coordinates and worries about the vertical / horizontal distinction throughout rather than using a much simpler relative approach.

An algorithm that treats the rendering as rendering a line from 0 to bar length would look like:

// this is psuedo rust, please forgive any errors
let length = length_of_bar_without_arrows(); // e.g. either area.width or area.height -1 or -2 
let (start_track, end_track) = relative_positions_of_track_without_arrows(); // e.g. (0, length - 1) or (1, length -2);
let (start_thumb, end_thumb) = relative_positions_of_thumb(); // e.g. (3, 6)

// this can be a method... absolute_positions()
let (left, top, right, bottom) =
  (area.x, area.y, area.x + area.width - 1, area.y + area.height - 1);
let (start_x, start_y, end_x, end_y) = match position {
  Position::HorizontalTop => (left, top, right, top),
  Position::HorizontalBottom => (left, bottom, right, bottom),
  Position::VerticalLeft => (left, top, left, bottom),
  Position::VerticalRight => right, top, right, bottom),
};

index = 0;
for x in (start_x..end_x) { // either this line
  for y in (start_y..end_y) { // or this line will just be a single iteration
    if index < start_track { draw_start_arrow(x, y); }
    else if index < start_thumb { draw_track(x, y); }
    else if index < end_thumb { draw_thumb(x, y); }
    else if index < end_track { draw_track(x, y); }
    else { draw_end_arrow(x, y); }
    index++;
  }
}

If I have indeed missed some complexity that is non obvious here, I apologize.

src/widgets/scrollbar.rs Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
@kdheepak
Copy link
Collaborator Author

kdheepak commented Jun 17, 2023

I guess the cross is confusing naming.

I made two changes based on your most recent comment:

  1. I changed all uses for size to start and end. Previously I was using the main_position and main_size to render, now it uses start and end to render. Maybe that'll make it a little clearer?
  2. I renamed all the variables to more accurately match the exact part of the scrollbar they are used for, i.e. track, thumb, etc. (What was previously cross is now track_axis, and main_start is now track_start. And there's a corresponding track_end. Similarly there's also a thumb_start and thumb_end.)

Here's these concepts ascii visualized:

        //
        // For ScrollbarOrientation::VerticalRight
        //
        //                   ┌───────── track_axis  (x)
        //                   v
        //   ┌───────────────┐
        //   │               ║<──────── track_start (y1)
        //   │               █
        //   │               █
        //   │               ║
        //   │               ║<──────── track_end   (y2)
        //   └───────────────┘
        //
        // For ScrollbarOrientation::HorizontalBottom
        //
        //   ┌───────────────┐
        //   │               │
        //   │               │
        //   │               │
        //   └═══███═════════┘<──────── track_axis  (y)
        //    ^             ^
        //    │             └────────── track_end   (x2)
        //    │
        //    └──────────────────────── track_start (x1)
        //

If it is still not clear, I can refactor the render function to exactly the way you wrote it, i.e. with two for loops instead of one, and with more abstractions.

@mindoodoo mindoodoo changed the title Add scrollbar widget feat: add scrollbar widget Jun 17, 2023
Copy link
Member

@mindoodoo mindoodoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome stuff guys, looks great !!

Only a couple nit picks, not blocking per say. Looking forward to seeing this in the next release ! 🚀

examples/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
src/widgets/scrollbar.rs Outdated Show resolved Hide resolved
@kdheepak kdheepak requested a review from joshka June 17, 2023 14:20
Copy link
Member

@joshka joshka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@joshka
Copy link
Member

joshka commented Jun 17, 2023

To make this ready to merge, I'm going to push a rebase squash and cleanup the commit message

Represents a scrollbar widget that renders a track, thumb and arrows
either horizontally or vertically. State is kept in ScrollbarState, and
passed as a parameter to the render function.
@joshka joshka added this pull request to the merge queue Jun 17, 2023
Merged via the queue into ratatui-org:main with commit 130bdf8 Jun 17, 2023
10 checks passed
@kdheepak kdheepak deleted the kd/scrollbar-widget branch June 17, 2023 22:23
samyosm pushed a commit to samyosm/ratatui that referenced this pull request Jun 18, 2023
Represents a scrollbar widget that renders a track, thumb and arrows
either horizontally or vertically. State is kept in ScrollbarState, and
passed as a parameter to the render function.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants