diff --git a/examples/example.rs b/examples/example.rs index 947cf7b..2c9bbed 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -5,8 +5,9 @@ use crossterm::{ }; use ratatui::{ backend::{Backend, CrosstermBackend}, + layout::Margin, style::{Color, Modifier, Style}, - widgets::Block, + widgets::{Block, Scrollbar, ScrollbarOrientation}, Terminal, }; use std::error::Error; @@ -117,10 +118,16 @@ fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result<( loop { terminal.draw(|frame| { let area = frame.size(); - let widget = Tree::new(app.items.clone()) .expect("all item identifiers are unique") .block(Block::bordered().title(format!("Tree Widget {:?}", app.state))) + .scrollbar_margin(Margin::new(0, 1)) + .scrollbar(Some( + Scrollbar::new(ScrollbarOrientation::VerticalRight) + .begin_symbol(None) + .track_symbol(None) + .end_symbol(None), + )) .highlight_style( Style::new() .fg(Color::Black) diff --git a/src/lib.rs b/src/lib.rs index 60dde73..76a335f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,9 +11,9 @@ The user interaction state (like the current selection) is stored in the [`TreeS use std::collections::HashSet; use ratatui::buffer::Buffer; -use ratatui::layout::{Corner, Rect}; +use ratatui::layout::{Corner, Margin, Rect}; use ratatui::style::Style; -use ratatui::widgets::{Block, StatefulWidget, Widget}; +use ratatui::widgets::{Block, Scrollbar, ScrollbarState, StatefulWidget, Widget}; use unicode_width::UnicodeWidthStr; mod flatten; @@ -60,6 +60,8 @@ pub struct Tree<'a, Identifier> { items: Vec>, block: Option>, + scrollbar: Option>, + scrollbar_margin: Margin, start_corner: Corner, /// Style used as a base style for the widget style: Style, @@ -101,6 +103,8 @@ where Ok(Self { items, block: None, + scrollbar: None, + scrollbar_margin: Margin::new(0, 0), start_corner: Corner::TopLeft, style: Style::new(), highlight_style: Style::new(), @@ -118,6 +122,18 @@ where self } + #[must_use] + pub const fn scrollbar(mut self, scrollbar: Option>) -> Self { + self.scrollbar = scrollbar; + self + } + + #[must_use] + pub const fn scrollbar_margin(mut self, margin: Margin) -> Self { + self.scrollbar_margin = margin; + self + } + #[must_use] pub const fn start_corner(mut self, corner: Corner) -> Self { self.start_corner = corner; @@ -176,13 +192,13 @@ where type State = TreeState; #[allow(clippy::too_many_lines)] - fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { - buf.set_style(area, self.style); + fn render(self, full_area: Rect, buf: &mut Buffer, state: &mut Self::State) { + buf.set_style(full_area, self.style); // Get the inner area inside a possible block, otherwise use the full area - let area = self.block.map_or(area, |block| { - let inner_area = block.inner(area); - block.render(area, buf); + let area = self.block.map_or(full_area, |block| { + let inner_area = block.inner(full_area); + block.render(full_area, buf); inner_area }); @@ -235,6 +251,14 @@ where state.offset = start; state.ensure_selected_in_view_on_next_render = false; + if let Some(scrollbar) = self.scrollbar { + let mut scrollbar_state = ScrollbarState::new(visible.len()) + .position(start) + .viewport_content_length(available_height); + let scrollbar_area = full_area.inner(&self.scrollbar_margin); + scrollbar.render(scrollbar_area, buf, &mut scrollbar_state); + } + let blank_symbol = " ".repeat(self.highlight_symbol.width()); let mut current_height = 0;