scrollbar

This commit is contained in:
Robin Appelman 2024-07-25 22:28:43 +02:00
commit 7a9fb10037
2 changed files with 97 additions and 21 deletions

View file

@ -12,6 +12,7 @@ use ratatui::crossterm::terminal::{
}; };
use ratatui::crossterm::{event, ExecutableCommand}; use ratatui::crossterm::{event, ExecutableCommand};
use ratatui::prelude::*; use ratatui::prelude::*;
use ratatui::widgets::{Block, Borders, Scrollbar, ScrollbarOrientation};
use ratatui::Terminal; use ratatui::Terminal;
use std::io; use std::io;
use std::io::stdout; use std::io::stdout;
@ -29,7 +30,7 @@ pub fn run_ui(app: App) -> Result<(), UiError> {
stdout().execute(EnterAlternateScreen)?; stdout().execute(EnterAlternateScreen)?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
let mut ui_state = UiState::default(); let mut ui_state = UiState::new(&app);
while !matches!(ui_state, UiState::Quit) { while !matches!(ui_state, UiState::Quit) {
terminal.draw(|frame| ui(frame, &app, &mut ui_state))?; terminal.draw(|frame| ui(frame, &app, &mut ui_state))?;
@ -79,7 +80,10 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
match state { match state {
UiState::Quit => {} UiState::Quit => {}
UiState::MatchList { table_state } => { UiState::MatchList {
table_state,
scroll_state,
} => {
let selected = table_state.selected().unwrap_or(0); let selected = table_state.selected().unwrap_or(0);
let histogram = if selected == 0 { let histogram = if selected == 0 {
&app.all.histogram &app.all.histogram
@ -89,28 +93,75 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
} else { } else {
&app.unmatched.histogram &app.unmatched.histogram
}; };
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
.begin_symbol(Some(""))
.end_symbol(Some(""));
frame.render_widget(UiHistogram::new(histogram), layout[0]); frame.render_widget(UiHistogram::new(histogram), layout[0]);
frame.render_stateful_widget(match_list(app), layout[1], table_state); frame.render_stateful_widget(
match_list(app).block(Block::new().borders(Borders::RIGHT)),
layout[1],
table_state,
);
frame.render_stateful_widget(
scrollbar,
layout[1].inner(Margin {
vertical: 1,
horizontal: 0,
}),
scroll_state,
);
frame.render_widget(footer(app, page), layout[2]); frame.render_widget(footer(app, page), layout[2]);
} }
UiState::Match { UiState::Match {
result, result,
table_state, table_state,
scroll_state,
.. ..
} => { } => {
let selected_group = &result.grouped[table_state.selected().unwrap_or_default()]; let selected_group = &result.grouped[table_state.selected().unwrap_or_default()];
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
.begin_symbol(Some(""))
.end_symbol(Some(""));
frame.render_widget(UiHistogram::new(&selected_group.histogram), layout[0]); frame.render_widget(UiHistogram::new(&selected_group.histogram), layout[0]);
frame.render_stateful_widget(grouped_lines(app, result), layout[1], table_state); frame.render_stateful_widget(
grouped_lines(app, result).block(Block::new().borders(Borders::RIGHT)),
layout[1],
table_state,
);
frame.render_stateful_widget(
scrollbar,
layout[1].inner(Margin {
vertical: 1,
horizontal: 0,
}),
scroll_state,
);
frame.render_widget(footer(app, page), layout[2]); frame.render_widget(footer(app, page), layout[2]);
} }
UiState::Logs { UiState::Logs {
lines, table_state, .. lines,
table_state,
scroll_state,
..
} => { } => {
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
.begin_symbol(Some(""))
.end_symbol(Some(""));
frame.render_stateful_widget( frame.render_stateful_widget(
raw_logs(app, lines), raw_logs(app, lines).block(Block::new().borders(Borders::RIGHT)),
layout[0].union(layout[1]), layout[0].union(layout[1]),
table_state, table_state,
); );
frame.render_stateful_widget(
scrollbar,
layout[0].union(layout[1]).inner(Margin {
vertical: 1,
horizontal: 0,
}),
scroll_state,
);
frame.render_widget(footer(app, page), layout[2]); frame.render_widget(footer(app, page), layout[2]);
} }
} }

View file

@ -1,34 +1,38 @@
use crate::app::{App, LogMatch}; use crate::app::{App, LogMatch};
use ratatui::widgets::TableState; use ratatui::widgets::{ScrollbarState, TableState};
use table_state::TableStateExt; use table_state::TableStateExt;
#[derive(Clone)] #[derive(Clone)]
pub enum UiState<'a> { pub enum UiState<'a> {
MatchList { MatchList {
table_state: TableState, table_state: TableState,
scroll_state: ScrollbarState,
}, },
Match { Match {
result: &'a LogMatch, result: &'a LogMatch,
table_state: TableState, table_state: TableState,
scroll_state: ScrollbarState,
previous: Box<UiState<'a>>, previous: Box<UiState<'a>>,
}, },
Logs { Logs {
lines: &'a [usize], lines: &'a [usize],
table_state: TableState, table_state: TableState,
scroll_state: ScrollbarState,
previous: Box<UiState<'a>>, previous: Box<UiState<'a>>,
}, },
Quit, Quit,
} }
impl Default for UiState<'_> { impl<'a> UiState<'a> {
fn default() -> Self { pub fn new(app: &App) -> Self {
let mut table_state = TableState::default(); let mut table_state = TableState::default();
table_state.select(Some(0)); table_state.select(Some(0));
UiState::MatchList { table_state } UiState::MatchList {
table_state,
scroll_state: ScrollbarState::new(app.match_lines()),
}
} }
}
impl<'a> UiState<'a> {
pub fn page(&self) -> UiPage { pub fn page(&self) -> UiPage {
match self { match self {
UiState::Quit | UiState::MatchList { .. } => UiPage::MatchList, UiState::Quit | UiState::MatchList { .. } => UiPage::MatchList,
@ -39,13 +43,22 @@ impl<'a> UiState<'a> {
fn table_state(&mut self) -> Option<&mut TableState> { fn table_state(&mut self) -> Option<&mut TableState> {
match self { match self {
UiState::MatchList { table_state } => Some(table_state), UiState::MatchList { table_state, .. } => Some(table_state),
UiState::Match { table_state, .. } => Some(table_state), UiState::Match { table_state, .. } => Some(table_state),
UiState::Logs { table_state, .. } => Some(table_state), UiState::Logs { table_state, .. } => Some(table_state),
UiState::Quit => None, UiState::Quit => None,
} }
} }
fn scroll_state(&mut self) -> Option<&mut ScrollbarState> {
match self {
UiState::MatchList { scroll_state, .. } => Some(scroll_state),
UiState::Match { scroll_state, .. } => Some(scroll_state),
UiState::Logs { scroll_state, .. } => Some(scroll_state),
UiState::Quit => None,
}
}
fn table_count(&self, app: &App) -> usize { fn table_count(&self, app: &App) -> usize {
match self { match self {
UiState::MatchList { .. } => app.match_lines(), UiState::MatchList { .. } => app.match_lines(),
@ -63,20 +76,25 @@ impl<'a> UiState<'a> {
(mut state, UiEvent::Down(step)) => { (mut state, UiEvent::Down(step)) => {
let count = state.table_count(app); let count = state.table_count(app);
if let Some(table_state) = state.table_state() { if let Some(table_state) = state.table_state() {
table_state.down(count, step) let pos = table_state.down(count, step);
let scroll_state = state.scroll_state().unwrap();
*scroll_state = scroll_state.position(pos);
} }
state state
} }
(mut state, UiEvent::Up(step)) => { (mut state, UiEvent::Up(step)) => {
let count = state.table_count(app); let count = state.table_count(app);
if let Some(table_state) = state.table_state() { if let Some(table_state) = state.table_state() {
table_state.up(count, step) let pos = table_state.up(count, step);
let scroll_state = state.scroll_state().unwrap();
*scroll_state = scroll_state.position(pos);
} }
state state
} }
( (
UiState::MatchList { UiState::MatchList {
table_state: prev_state, table_state: prev_state,
scroll_state: prev_scroll,
}, },
UiEvent::Select, UiEvent::Select,
) => { ) => {
@ -94,14 +112,17 @@ impl<'a> UiState<'a> {
UiState::Match { UiState::Match {
result, result,
table_state, table_state,
scroll_state: ScrollbarState::new(result.count()),
previous: Box::new(UiState::MatchList { previous: Box::new(UiState::MatchList {
table_state: prev_state, table_state: prev_state,
scroll_state: prev_scroll,
}), }),
} }
} }
( (
UiState::Match { UiState::Match {
table_state: prev_state, table_state: prev_state,
scroll_state: prev_scroll,
previous, previous,
result, result,
}, },
@ -115,8 +136,10 @@ impl<'a> UiState<'a> {
UiState::Logs { UiState::Logs {
lines, lines,
table_state, table_state,
scroll_state: ScrollbarState::new(lines.len()),
previous: Box::new(UiState::Match { previous: Box::new(UiState::Match {
table_state: prev_state, table_state: prev_state,
scroll_state: prev_scroll,
previous, previous,
result, result,
}), }),
@ -148,29 +171,31 @@ mod table_state {
use ratatui::widgets::TableState; use ratatui::widgets::TableState;
pub trait TableStateExt { pub trait TableStateExt {
fn up(&mut self, count: usize, step: usize); fn up(&mut self, count: usize, step: usize) -> usize;
fn down(&mut self, count: usize, step: usize); fn down(&mut self, count: usize, step: usize) -> usize;
} }
impl TableStateExt for TableState { impl TableStateExt for TableState {
fn up(&mut self, count: usize, step: usize) { fn up(&mut self, count: usize, step: usize) -> usize {
let current = self.selected().unwrap_or(0); let current = self.selected().unwrap_or(0);
let after = if step > current { let after = if step > current {
count - 1 count - 1
} else { } else {
current - step current - step
}; };
self.select(Some(after)) self.select(Some(after));
after
} }
fn down(&mut self, count: usize, step: usize) { fn down(&mut self, count: usize, step: usize) -> usize {
let current = self.selected().unwrap_or(0); let current = self.selected().unwrap_or(0);
let after = if step >= count - current { let after = if step >= count - current {
0 0
} else { } else {
current + step current + step
}; };
self.select(Some(after)) self.select(Some(after));
after
} }
} }
} }