feat: support for a scrollbar

This commit is contained in:
EdJoPaTo
2024-02-22 19:20:27 +01:00
parent 27cf513eeb
commit 92466615b1
2 changed files with 40 additions and 9 deletions
+9 -2
View File
@@ -5,8 +5,9 @@ use crossterm::{
}; };
use ratatui::{ use ratatui::{
backend::{Backend, CrosstermBackend}, backend::{Backend, CrosstermBackend},
layout::Margin,
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
widgets::Block, widgets::{Block, Scrollbar, ScrollbarOrientation},
Terminal, Terminal,
}; };
use std::error::Error; use std::error::Error;
@@ -117,10 +118,16 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
loop { loop {
terminal.draw(|frame| { terminal.draw(|frame| {
let area = frame.size(); let area = frame.size();
let widget = Tree::new(app.items.clone()) let widget = Tree::new(app.items.clone())
.expect("all item identifiers are unique") .expect("all item identifiers are unique")
.block(Block::bordered().title(format!("Tree Widget {:?}", app.state))) .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( .highlight_style(
Style::new() Style::new()
.fg(Color::Black) .fg(Color::Black)
+31 -7
View File
@@ -11,9 +11,9 @@ The user interaction state (like the current selection) is stored in the [`TreeS
use std::collections::HashSet; use std::collections::HashSet;
use ratatui::buffer::Buffer; use ratatui::buffer::Buffer;
use ratatui::layout::{Corner, Rect}; use ratatui::layout::{Corner, Margin, Rect};
use ratatui::style::Style; use ratatui::style::Style;
use ratatui::widgets::{Block, StatefulWidget, Widget}; use ratatui::widgets::{Block, Scrollbar, ScrollbarState, StatefulWidget, Widget};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
mod flatten; mod flatten;
@@ -60,6 +60,8 @@ pub struct Tree<'a, Identifier> {
items: Vec<TreeItem<'a, Identifier>>, items: Vec<TreeItem<'a, Identifier>>,
block: Option<Block<'a>>, block: Option<Block<'a>>,
scrollbar: Option<Scrollbar<'a>>,
scrollbar_margin: Margin,
start_corner: Corner, start_corner: Corner,
/// Style used as a base style for the widget /// Style used as a base style for the widget
style: Style, style: Style,
@@ -101,6 +103,8 @@ where
Ok(Self { Ok(Self {
items, items,
block: None, block: None,
scrollbar: None,
scrollbar_margin: Margin::new(0, 0),
start_corner: Corner::TopLeft, start_corner: Corner::TopLeft,
style: Style::new(), style: Style::new(),
highlight_style: Style::new(), highlight_style: Style::new(),
@@ -118,6 +122,18 @@ where
self self
} }
#[must_use]
pub const fn scrollbar(mut self, scrollbar: Option<Scrollbar<'a>>) -> Self {
self.scrollbar = scrollbar;
self
}
#[must_use]
pub const fn scrollbar_margin(mut self, margin: Margin) -> Self {
self.scrollbar_margin = margin;
self
}
#[must_use] #[must_use]
pub const fn start_corner(mut self, corner: Corner) -> Self { pub const fn start_corner(mut self, corner: Corner) -> Self {
self.start_corner = corner; self.start_corner = corner;
@@ -176,13 +192,13 @@ where
type State = TreeState<Identifier>; type State = TreeState<Identifier>;
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { fn render(self, full_area: Rect, buf: &mut Buffer, state: &mut Self::State) {
buf.set_style(area, self.style); buf.set_style(full_area, self.style);
// Get the inner area inside a possible block, otherwise use the full area // Get the inner area inside a possible block, otherwise use the full area
let area = self.block.map_or(area, |block| { let area = self.block.map_or(full_area, |block| {
let inner_area = block.inner(area); let inner_area = block.inner(full_area);
block.render(area, buf); block.render(full_area, buf);
inner_area inner_area
}); });
@@ -235,6 +251,14 @@ where
state.offset = start; state.offset = start;
state.ensure_selected_in_view_on_next_render = false; 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 blank_symbol = " ".repeat(self.highlight_symbol.width());
let mut current_height = 0; let mut current_height = 0;