mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-22 12:43:16 +00:00
Better ergonomics for ScrollbarState and improved documentation (#456)
* feat(scrollbar): Better ergonomics for ScrollbarState and improved documentation ✨ * feat(scrollbar)!: Use usize instead of u16 for scrollbar ✨ 💥
This commit is contained in:
parent
927a5d8251
commit
0696f484e8
3 changed files with 90 additions and 26 deletions
|
@ -66,27 +66,23 @@ fn run_app<B: Backend>(
|
||||||
KeyCode::Char('q') => return Ok(()),
|
KeyCode::Char('q') => return Ok(()),
|
||||||
KeyCode::Char('j') => {
|
KeyCode::Char('j') => {
|
||||||
app.vertical_scroll = app.vertical_scroll.saturating_add(1);
|
app.vertical_scroll = app.vertical_scroll.saturating_add(1);
|
||||||
app.vertical_scroll_state = app
|
app.vertical_scroll_state =
|
||||||
.vertical_scroll_state
|
app.vertical_scroll_state.position(app.vertical_scroll);
|
||||||
.position(app.vertical_scroll as u16);
|
|
||||||
}
|
}
|
||||||
KeyCode::Char('k') => {
|
KeyCode::Char('k') => {
|
||||||
app.vertical_scroll = app.vertical_scroll.saturating_sub(1);
|
app.vertical_scroll = app.vertical_scroll.saturating_sub(1);
|
||||||
app.vertical_scroll_state = app
|
app.vertical_scroll_state =
|
||||||
.vertical_scroll_state
|
app.vertical_scroll_state.position(app.vertical_scroll);
|
||||||
.position(app.vertical_scroll as u16);
|
|
||||||
}
|
}
|
||||||
KeyCode::Char('h') => {
|
KeyCode::Char('h') => {
|
||||||
app.horizontal_scroll = app.horizontal_scroll.saturating_sub(1);
|
app.horizontal_scroll = app.horizontal_scroll.saturating_sub(1);
|
||||||
app.horizontal_scroll_state = app
|
app.horizontal_scroll_state =
|
||||||
.horizontal_scroll_state
|
app.horizontal_scroll_state.position(app.horizontal_scroll);
|
||||||
.position(app.horizontal_scroll as u16);
|
|
||||||
}
|
}
|
||||||
KeyCode::Char('l') => {
|
KeyCode::Char('l') => {
|
||||||
app.horizontal_scroll = app.horizontal_scroll.saturating_add(1);
|
app.horizontal_scroll = app.horizontal_scroll.saturating_add(1);
|
||||||
app.horizontal_scroll_state = app
|
app.horizontal_scroll_state =
|
||||||
.horizontal_scroll_state
|
app.horizontal_scroll_state.position(app.horizontal_scroll);
|
||||||
.position(app.horizontal_scroll as u16);
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -151,10 +147,8 @@ fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
app.vertical_scroll_state = app.vertical_scroll_state.content_length(text.len() as u16);
|
app.vertical_scroll_state = app.vertical_scroll_state.content_length(text.len());
|
||||||
app.horizontal_scroll_state = app
|
app.horizontal_scroll_state = app.horizontal_scroll_state.content_length(long_line.len());
|
||||||
.horizontal_scroll_state
|
|
||||||
.content_length(long_line.len() as u16);
|
|
||||||
|
|
||||||
let create_block = |title| {
|
let create_block = |title| {
|
||||||
Block::default()
|
Block::default()
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
//! - [`BarChart`]
|
//! - [`BarChart`]
|
||||||
//! - [`Gauge`]
|
//! - [`Gauge`]
|
||||||
//! - [`Sparkline`]
|
//! - [`Sparkline`]
|
||||||
|
//! - [`Scrollbar`]
|
||||||
//! - [`calendar::Monthly`]
|
//! - [`calendar::Monthly`]
|
||||||
//! - [`Clear`]
|
//! - [`Clear`]
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,16 @@ pub enum ScrollDirection {
|
||||||
|
|
||||||
/// A struct representing the state of a Scrollbar widget.
|
/// A struct representing the state of a Scrollbar widget.
|
||||||
///
|
///
|
||||||
|
/// # Important
|
||||||
|
///
|
||||||
|
/// It's essential to set the `content_length` field when using this struct. This field
|
||||||
|
/// represents the total length of the scrollable content. The default value is zero
|
||||||
|
/// which will result in the Scrollbar not rendering.
|
||||||
|
///
|
||||||
/// For example, in the following list, assume there are 4 bullet points:
|
/// For example, in the following list, assume there are 4 bullet points:
|
||||||
///
|
///
|
||||||
/// - the `position` is 0
|
|
||||||
/// - the `content_length` is 4
|
/// - the `content_length` is 4
|
||||||
|
/// - the `position` is 0
|
||||||
/// - the `viewport_content_length` is 2
|
/// - the `viewport_content_length` is 2
|
||||||
///
|
///
|
||||||
/// ```text
|
/// ```text
|
||||||
|
@ -39,29 +45,36 @@ pub enum ScrollDirection {
|
||||||
/// default of 0 and it'll use the track size as a `viewport_content_length`.
|
/// default of 0 and it'll use the track size as a `viewport_content_length`.
|
||||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
pub struct ScrollbarState {
|
pub struct ScrollbarState {
|
||||||
// The current position within the scrollable content.
|
|
||||||
position: u16,
|
|
||||||
// The total length of the scrollable content.
|
// The total length of the scrollable content.
|
||||||
content_length: u16,
|
content_length: usize,
|
||||||
|
// The current position within the scrollable content.
|
||||||
|
position: usize,
|
||||||
// The length of content in current viewport.
|
// The length of content in current viewport.
|
||||||
viewport_content_length: u16,
|
viewport_content_length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScrollbarState {
|
impl ScrollbarState {
|
||||||
|
/// Constructs a new ScrollbarState with the specified content length.
|
||||||
|
pub fn new(content_length: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
content_length,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Sets the scroll position of the scrollbar and returns the modified ScrollbarState.
|
/// Sets the scroll position of the scrollbar and returns the modified ScrollbarState.
|
||||||
pub fn position(mut self, position: u16) -> Self {
|
pub fn position(mut self, position: usize) -> Self {
|
||||||
self.position = position;
|
self.position = position;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the length of the scrollable content and returns the modified ScrollbarState.
|
/// Sets the length of the scrollable content and returns the modified ScrollbarState.
|
||||||
pub fn content_length(mut self, content_length: u16) -> Self {
|
pub fn content_length(mut self, content_length: usize) -> Self {
|
||||||
self.content_length = content_length;
|
self.content_length = content_length;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the length of the viewport content and returns the modified ScrollbarState.
|
/// Sets the length of the viewport content and returns the modified ScrollbarState.
|
||||||
pub fn viewport_content_length(mut self, viewport_content_length: u16) -> Self {
|
pub fn viewport_content_length(mut self, viewport_content_length: usize) -> Self {
|
||||||
self.viewport_content_length = viewport_content_length;
|
self.viewport_content_length = viewport_content_length;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -124,6 +137,37 @@ pub enum ScrollbarOrientation {
|
||||||
/// │ └──────── thumb
|
/// │ └──────── thumb
|
||||||
/// └─────────── begin
|
/// └─────────── begin
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use ratatui::prelude::*;
|
||||||
|
/// # use ratatui::widgets::*;
|
||||||
|
/// # fn render_paragraph_with_scrollbar<B: Backend>(frame: &mut Frame<B>, area: Rect) {
|
||||||
|
///
|
||||||
|
/// let vertical_scroll = 0; // from app state
|
||||||
|
///
|
||||||
|
/// let items = vec![Line::from("Item 1"), Line::from("Item 2"), Line::from("Item 3")];
|
||||||
|
/// let paragraph = Paragraph::new(items.clone())
|
||||||
|
/// .scroll((vertical_scroll as u16, 0))
|
||||||
|
/// .block(Block::new().borders(Borders::RIGHT)); // to show a background for the scrollbar
|
||||||
|
///
|
||||||
|
/// let scrollbar = Scrollbar::default()
|
||||||
|
/// .orientation(ScrollbarOrientation::VerticalRight)
|
||||||
|
/// .begin_symbol(Some("↑"))
|
||||||
|
/// .end_symbol(Some("↓"));
|
||||||
|
/// let mut scrollbar_state = ScrollbarState::new(items.iter().len()).position(vertical_scroll);
|
||||||
|
///
|
||||||
|
/// let area = frame.size();
|
||||||
|
/// frame.render_widget(paragraph, area);
|
||||||
|
/// frame.render_stateful_widget(scrollbar,
|
||||||
|
/// area.inner(&Margin {
|
||||||
|
/// vertical: 1,
|
||||||
|
/// horizontal: 0,
|
||||||
|
/// }), // using a inner vertical margin of 1 unit makes the scrollbar inside the block
|
||||||
|
/// &mut scrollbar_state);
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Scrollbar<'a> {
|
pub struct Scrollbar<'a> {
|
||||||
orientation: ScrollbarOrientation,
|
orientation: ScrollbarOrientation,
|
||||||
|
@ -312,7 +356,7 @@ impl<'a> Scrollbar<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_not_render(&self, track_start: u16, track_end: u16, content_length: u16) -> bool {
|
fn should_not_render(&self, track_start: u16, track_end: u16, content_length: usize) -> bool {
|
||||||
if track_end - track_start == 0 || content_length == 0 {
|
if track_end - track_start == 0 || content_length == 0 {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -363,7 +407,7 @@ impl<'a> Scrollbar<'a> {
|
||||||
let (track_start, track_end) = track_start_end;
|
let (track_start, track_end) = track_start_end;
|
||||||
|
|
||||||
let viewport_content_length = if state.viewport_content_length == 0 {
|
let viewport_content_length = if state.viewport_content_length == 0 {
|
||||||
track_end - track_start
|
(track_end - track_start) as usize
|
||||||
} else {
|
} else {
|
||||||
state.viewport_content_length
|
state.viewport_content_length
|
||||||
};
|
};
|
||||||
|
@ -534,6 +578,31 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_renders_empty_with_content_length_is_zero() {
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 8));
|
||||||
|
let mut state = ScrollbarState::default().position(0);
|
||||||
|
Scrollbar::default()
|
||||||
|
.begin_symbol(None)
|
||||||
|
.end_symbol(None)
|
||||||
|
.render(buffer.area, &mut buffer, &mut state);
|
||||||
|
assert_buffer_eq!(
|
||||||
|
buffer,
|
||||||
|
Buffer::with_lines(vec![" ", " ", " ", " ", " ", " ", " ", " "])
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 2, 8));
|
||||||
|
let mut state = ScrollbarState::new(8).position(0);
|
||||||
|
Scrollbar::default()
|
||||||
|
.begin_symbol(None)
|
||||||
|
.end_symbol(None)
|
||||||
|
.render(buffer.area, &mut buffer, &mut state);
|
||||||
|
assert_buffer_eq!(
|
||||||
|
buffer,
|
||||||
|
Buffer::with_lines(vec![" █", " █", " █", " █", " █", " █", " █", " █"])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_render_when_area_zero() {
|
fn test_no_render_when_area_zero() {
|
||||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 0, 0));
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 0, 0));
|
||||||
|
|
Loading…
Reference in a new issue