feat: support for a scrollbar
This commit is contained in:
+9
-2
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user