Scrollbar Demo
Demonstrates rendering different types of scrollbars. Source scrollbar.
git clone https://github.com/ratatui/ratatui.git --branch ratatui-v0.30.2 --depth 1cd ratatuicargo run -p scrollbar
/// A Ratatui example that demonstrates how to handle scrollbars.////// This example demonstrates how to draw various types of vertical and horizontal scrollbars/// with different styles.////// This example runs with the Ratatui library code in the branch that you are currently/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui/// release.////// [`latest`]: https://github.com/ratatui/ratatui/tree/latestuse std::rc::Rc;use std::time::{Duration, Instant};
use color_eyre::Result;use crossterm::event::{self, KeyCode};use ratatui::layout::{Alignment, Constraint, Layout, Margin, Rect};use ratatui::style::{Color, Style, Stylize};use ratatui::symbols::scrollbar;use ratatui::text::{Line, Masked, Span};use ratatui::widgets::{Block, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState};use ratatui::{DefaultTerminal, Frame};
#[derive(Default)]struct App { pub vertical_scroll_state: ScrollbarState, pub horizontal_scroll_state: ScrollbarState, pub vertical_scroll: usize, pub horizontal_scroll: usize,}
fn main() -> Result<()> { color_eyre::install()?; ratatui::run(|terminal| App::default().run(terminal))}
impl App { fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> { let tick_rate = Duration::from_millis(250); let mut last_tick = Instant::now(); loop { terminal.draw(|frame| self.render(frame))?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed()); if !event::poll(timeout)? { last_tick = Instant::now(); continue; } if let Some(key) = event::read()?.as_key_press_event() { match key.code { KeyCode::Char('q') => return Ok(()), KeyCode::Char('j') | KeyCode::Down => self.scroll_down(), KeyCode::Char('k') | KeyCode::Up => self.scroll_up(), KeyCode::Char('h') | KeyCode::Left => self.scroll_left(), KeyCode::Char('l') | KeyCode::Right => self.scroll_right(), _ => {} } } } }
fn create_block(title: &str) -> Block<'_> { Block::bordered().gray().title(title.bold()) }
const fn scroll_down(&mut self) { self.vertical_scroll = self.vertical_scroll.saturating_add(1); self.vertical_scroll_state = self.vertical_scroll_state.position(self.vertical_scroll); }
const fn scroll_up(&mut self) { self.vertical_scroll = self.vertical_scroll.saturating_sub(1); self.vertical_scroll_state = self.vertical_scroll_state.position(self.vertical_scroll); }
const fn scroll_left(&mut self) { self.horizontal_scroll = self.horizontal_scroll.saturating_sub(1); self.horizontal_scroll_state = self .horizontal_scroll_state .position(self.horizontal_scroll); }
const fn scroll_right(&mut self) { self.horizontal_scroll = self.horizontal_scroll.saturating_add(1); self.horizontal_scroll_state = self .horizontal_scroll_state .position(self.horizontal_scroll); }
fn render_vertical_scroll(&mut self, frame: &mut Frame, text: &[Line], chunks: &Rc<[Rect]>) { let title = Block::new() .title_alignment(Alignment::Center) .title("Use h j k l or ◄ ▲ ▼ ► to scroll ".bold()); frame.render_widget(title, chunks[0]);
let paragraph = Paragraph::new(text.to_owned()) .gray() .block(Self::create_block("Vertical scrollbar with arrows")) .scroll((self.vertical_scroll as u16, 0)); frame.render_widget(paragraph, chunks[1]); frame.render_stateful_widget( Scrollbar::new(ScrollbarOrientation::VerticalRight) .begin_symbol(Some("↑")) .end_symbol(Some("↓")), chunks[1], &mut self.vertical_scroll_state, );
let paragraph = Paragraph::new(text.to_owned()) .gray() .block(Self::create_block( "Vertical scrollbar without arrows, without track symbol and mirrored", )) .scroll((self.vertical_scroll as u16, 0)); frame.render_widget(paragraph, chunks[2]); frame.render_stateful_widget( Scrollbar::new(ScrollbarOrientation::VerticalLeft) .symbols(scrollbar::VERTICAL) .begin_symbol(None) .track_symbol(None) .end_symbol(None), chunks[2].inner(Margin { vertical: 1, horizontal: 0, }), &mut self.vertical_scroll_state, ); }
fn render_horizontal_scroll(&mut self, frame: &mut Frame, text: &[Line], chunks: &Rc<[Rect]>) { let paragraph = Paragraph::new(text.to_owned()) .gray() .block(Self::create_block( "Horizontal scrollbar with only begin arrow & custom thumb symbol", )) .scroll((0, self.horizontal_scroll as u16)); frame.render_widget(paragraph, chunks[3]); frame.render_stateful_widget( Scrollbar::new(ScrollbarOrientation::HorizontalBottom) .thumb_symbol("🬋") .end_symbol(None), chunks[3].inner(Margin { vertical: 0, horizontal: 1, }), &mut self.horizontal_scroll_state, );
let paragraph = Paragraph::new(text.to_owned()) .gray() .block(Self::create_block( "Horizontal scrollbar without arrows & custom thumb and track symbol", )) .scroll((0, self.horizontal_scroll as u16)); frame.render_widget(paragraph, chunks[4]); frame.render_stateful_widget( Scrollbar::new(ScrollbarOrientation::HorizontalBottom) .thumb_symbol("░") .track_symbol(Some("─")), chunks[4].inner(Margin { vertical: 0, horizontal: 1, }), &mut self.horizontal_scroll_state, ); }
fn render(&mut self, frame: &mut Frame) { let area = frame.area();
// Words made "loooong" to demonstrate line breaking. let s = "Veeeeeeeeeeeeeeeery loooooooooooooooooong striiiiiiiiiiiiiiiiiiiiiiiiiing. "; let mut long_line = s.repeat(usize::from(area.width) / s.len() + 4); long_line.push('\n');
let chunks = Layout::vertical([ Constraint::Min(1), Constraint::Percentage(25), Constraint::Percentage(25), Constraint::Percentage(25), Constraint::Percentage(25), ]) .split(area);
let text = vec![ Line::from("This is a line "), Line::from("This is a line ".red()), Line::from("This is a line".on_dark_gray()), Line::from("This is a longer line".crossed_out()), Line::from(long_line.clone()), Line::from("This is a line".reset()), Span::raw("Masked text: ") + Span::styled(Masked::new("password", '*'), Style::new().fg(Color::Red)), Line::from("This is a line "), Line::from("This is a line ".red()), Line::from("This is a line".on_dark_gray()), Line::from("This is a longer line".crossed_out()), Line::from(long_line.clone()), Line::from("This is a line".reset()), Span::raw("Masked text: ") + Span::styled(Masked::new("password", '*'), Style::new().fg(Color::Red)), ]; self.vertical_scroll_state = self.vertical_scroll_state.content_length(text.len()); self.horizontal_scroll_state = self.horizontal_scroll_state.content_length(long_line.len());
self.render_vertical_scroll(frame, &text, &chunks); self.render_horizontal_scroll(frame, &text, &chunks); }}