From 922b1d96e225fed444a2feddf59a67bb892a76bb Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 9 Jul 2021 10:43:31 +0900 Subject: [PATCH] horizontal scroll --- src/components/databases.rs | 2 +- src/components/table.rs | 32 +++++-- src/components/utils/horizontal_scroll.rs | 92 +++++++++++++++++++ src/components/utils/mod.rs | 3 +- ...{scroll_vertical.rs => vertical_scroll.rs} | 6 +- src/main.rs | 15 ++- src/ui/scrollbar.rs | 67 +++++++++++--- 7 files changed, 182 insertions(+), 35 deletions(-) create mode 100644 src/components/utils/horizontal_scroll.rs rename src/components/utils/{scroll_vertical.rs => vertical_scroll.rs} (96%) diff --git a/src/components/databases.rs b/src/components/databases.rs index 1e38519..5351f7b 100644 --- a/src/components/databases.rs +++ b/src/components/databases.rs @@ -1,4 +1,4 @@ -use super::{utils::scroll_vertical::VerticalScroll, Component, DrawableComponent}; +use super::{utils::vertical_scroll::VerticalScroll, Component, DrawableComponent}; use crate::event::Key; use crate::ui::common_nav; use crate::ui::scrolllist::draw_list_block; diff --git a/src/components/table.rs b/src/components/table.rs index ad7be03..b445bdf 100644 --- a/src/components/table.rs +++ b/src/components/table.rs @@ -1,5 +1,5 @@ -use super::{utils::scroll_vertical::VerticalScroll, Component, DrawableComponent}; -use crate::event::Key; +use super::{utils::vertical_scroll::VerticalScroll, Component, DrawableComponent}; +use crate::{components::utils::horizontal_scroll::HorizontalScroll, event::Key}; use anyhow::Result; use std::convert::From; use tui::{ @@ -16,7 +16,8 @@ pub struct TableComponent { pub rows: Vec>, pub column_index: usize, pub column_page: usize, - pub scroll: VerticalScroll, + pub vertical_scroll: VerticalScroll, + pub horizontal_scroll: HorizontalScroll, pub select_entire_row: bool, } @@ -28,7 +29,8 @@ impl Default for TableComponent { rows: vec![], column_page: 0, column_index: 0, - scroll: VerticalScroll::new(), + vertical_scroll: VerticalScroll::new(), + horizontal_scroll: HorizontalScroll::new(), select_entire_row: false, } } @@ -95,7 +97,7 @@ impl TableComponent { if self.rows.is_empty() { return; } - if self.column_index == self.headers.len() - 1 { + if self.column_index == self.headers.len() { return; } if self.column_index == 9 { @@ -163,10 +165,10 @@ impl DrawableComponent for TableComponent { fn draw(&mut self, f: &mut Frame, area: Rect, focused: bool) -> Result<()> { self.state.selected().map_or_else( || { - self.scroll.reset(); + self.vertical_scroll.reset(); }, |selection| { - self.scroll.update( + self.vertical_scroll.update( selection, self.rows.len(), area.height.saturating_sub(2) as usize, @@ -174,6 +176,19 @@ impl DrawableComponent for TableComponent { }, ); + let column_width = area.width.saturating_pow(10); + self.horizontal_scroll.update( + if self.headers.is_empty() { + 0 + } else { + column_width + .saturating_mul(self.headers[..self.column_index].len() as u16) + .saturating_add(1) as usize + }, + column_width.saturating_mul(self.headers.len() as u16) as usize, + area.width.saturating_sub(2) as usize, + ); + let headers = self.headers(); let header_cells = headers .iter() @@ -224,7 +239,8 @@ impl DrawableComponent for TableComponent { .widths(&widths); f.render_stateful_widget(t, area, &mut self.state); - self.scroll.draw(f, area); + self.vertical_scroll.draw(f, area); + self.horizontal_scroll.draw(f, area); Ok(()) } } diff --git a/src/components/utils/horizontal_scroll.rs b/src/components/utils/horizontal_scroll.rs new file mode 100644 index 0000000..b4f16dc --- /dev/null +++ b/src/components/utils/horizontal_scroll.rs @@ -0,0 +1,92 @@ +use crate::{components::ScrollType, ui::scrollbar::draw_scrollbar}; +use std::{cell::Cell, thread::panicking}; +use tui::{backend::Backend, layout::Rect, Frame}; + +pub struct HorizontalScroll { + right: Cell, + max_right: Cell, +} + +impl HorizontalScroll { + pub const fn new() -> Self { + Self { + right: Cell::new(0), + max_right: Cell::new(0), + } + } + + pub fn get_right(&self) -> usize { + self.right.get() + } + + pub fn reset(&self) { + self.right.set(0); + } + + pub fn _move_right(&self, move_type: ScrollType) -> bool { + let old = self.right.get(); + let max = self.max_right.get(); + + let new_scroll_right = match move_type { + ScrollType::Down => old.saturating_add(1), + ScrollType::Up => old.saturating_sub(1), + ScrollType::Home => 0, + ScrollType::End => max, + _ => old, + }; + + let new_scroll_right = new_scroll_right.clamp(0, max); + + if new_scroll_right == old { + return false; + } + + self.right.set(new_scroll_right); + + true + } + + pub fn update(&self, selection: usize, selection_max: usize, visual_width: usize) -> usize { + let new_right = calc_scroll_right(self.get_right(), visual_width, selection, selection_max); + self.right.set(new_right); + + if visual_width == 0 { + self.max_right.set(0); + } else { + let new_max = selection_max.saturating_sub(visual_width); + self.max_right.set(new_max); + } + + new_right + } + + pub fn _update_no_selection(&self, line_count: usize, visual_width: usize) -> usize { + self.update(self.get_right(), line_count, visual_width) + } + + pub fn draw(&self, f: &mut Frame, r: Rect) { + draw_scrollbar(f, r, self.max_right.get(), self.right.get(), false); + } +} + +fn calc_scroll_right( + current_right: usize, + width_in_lines: usize, + selection: usize, + selection_max: usize, +) -> usize { + if width_in_lines == 0 { + return 0; + } + if selection_max <= width_in_lines { + return 0; + } + + if current_right + width_in_lines <= selection { + selection.saturating_sub(width_in_lines) + 1 + } else if current_right > selection { + selection + } else { + current_right + } +} diff --git a/src/components/utils/mod.rs b/src/components/utils/mod.rs index 5860c6c..dadc78b 100644 --- a/src/components/utils/mod.rs +++ b/src/components/utils/mod.rs @@ -1 +1,2 @@ -pub mod scroll_vertical; +pub mod horizontal_scroll; +pub mod vertical_scroll; diff --git a/src/components/utils/scroll_vertical.rs b/src/components/utils/vertical_scroll.rs similarity index 96% rename from src/components/utils/scroll_vertical.rs rename to src/components/utils/vertical_scroll.rs index ceb5fc6..9f85841 100644 --- a/src/components/utils/scroll_vertical.rs +++ b/src/components/utils/vertical_scroll.rs @@ -1,9 +1,7 @@ +use crate::{components::ScrollType, ui::scrollbar::draw_scrollbar}; use std::cell::Cell; - use tui::{backend::Backend, layout::Rect, Frame}; -use crate::{components::ScrollType, ui::scrollbar::draw_scrollbar}; - pub struct VerticalScroll { top: Cell, max_top: Cell, @@ -67,7 +65,7 @@ impl VerticalScroll { } pub fn draw(&self, f: &mut Frame, r: Rect) { - draw_scrollbar(f, r, self.max_top.get(), self.top.get()); + draw_scrollbar(f, r, self.max_top.get(), self.top.get(), true); } } diff --git a/src/main.rs b/src/main.rs index e93cfc2..39fdb2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,6 @@ async fn main() -> anyhow::Result<()> { let stdout = stdout(); setup_terminal()?; - set_panic_handlers()?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; @@ -70,13 +69,13 @@ fn setup_terminal() -> Result<()> { Ok(()) } -fn set_panic_handlers() -> Result<()> { - panic::set_hook(Box::new(|e| { - eprintln!("panic: {:?}", e); - shutdown_terminal(); - })); - Ok(()) -} +// fn set_panic_handlers() -> Result<()> { +// panic::set_hook(Box::new(|e| { +// println!("panic: {:?}", e); +// shutdown_terminal(); +// })); +// Ok(()) +// } fn shutdown_terminal() { let leave_screen = io::stdout().execute(LeaveAlternateScreen).map(|_f| ()); diff --git a/src/ui/scrollbar.rs b/src/ui/scrollbar.rs index 44d7f5e..4ed0155 100644 --- a/src/ui/scrollbar.rs +++ b/src/ui/scrollbar.rs @@ -5,7 +5,10 @@ use tui::{ buffer::Buffer, layout::{Margin, Rect}, style::Style, - symbols::{block::FULL, line::DOUBLE_VERTICAL}, + symbols::{ + block::FULL, + line::{DOUBLE_HORIZONTAL, DOUBLE_VERTICAL}, + }, widgets::Widget, Frame, }; @@ -16,22 +19,28 @@ struct Scrollbar { pos: u16, style_bar: Style, style_pos: Style, + vertical: bool, } impl Scrollbar { - fn new(max: usize, pos: usize) -> Self { + fn new(max: usize, pos: usize, vertical: bool) -> Self { Self { max: u16::try_from(max).unwrap_or_default(), pos: u16::try_from(pos).unwrap_or_default(), style_pos: Style::default(), style_bar: Style::default(), + vertical, } } } impl Widget for Scrollbar { fn render(self, area: Rect, buf: &mut Buffer) { - if area.height <= 2 { + if self.vertical && area.height <= 2 { + return; + } + + if !self.vertical && area.width <= 2 { return; } @@ -44,17 +53,39 @@ impl Widget for Scrollbar { return; }; + let bottom = area.bottom().saturating_sub(1); + if bottom <= area.top() { + return; + }; + let (bar_top, bar_height) = { - let scrollbar_area = area.inner(&Margin { - horizontal: 0, - vertical: 1, - }); + let scrollbar_area = if self.vertical { + area.inner(&Margin { + horizontal: 0, + vertical: 1, + }) + } else { + area.inner(&Margin { + horizontal: 1, + vertical: 0, + }) + }; - (scrollbar_area.top(), scrollbar_area.height) + if self.vertical { + (scrollbar_area.top(), scrollbar_area.height) + } else { + (scrollbar_area.left(), scrollbar_area.width) + } }; - for y in bar_top..(bar_top + bar_height) { - buf.set_string(right, y, DOUBLE_VERTICAL, self.style_bar); + if self.vertical { + for y in bar_top..(bar_top + bar_height) { + buf.set_string(right, y, DOUBLE_VERTICAL, self.style_bar) + } + } else { + for x in bar_top..(bar_top + bar_height) { + buf.set_string(x, bottom, DOUBLE_HORIZONTAL, self.style_bar) + } } let progress = f32::from(self.pos) / f32::from(self.max); @@ -64,12 +95,22 @@ impl Widget for Scrollbar { let pos: u16 = pos.cast_nearest(); let pos = pos.saturating_sub(1); - buf.set_string(right, bar_top + pos, FULL, self.style_pos); + if self.vertical { + buf.set_string(right, bar_top + pos, FULL, self.style_pos); + } else { + buf.set_string(bar_top + pos, bottom, "▆", self.style_pos); + } } } -pub fn draw_scrollbar(f: &mut Frame, r: Rect, max: usize, pos: usize) { - let mut widget = Scrollbar::new(max, pos); +pub fn draw_scrollbar( + f: &mut Frame, + r: Rect, + max: usize, + pos: usize, + vertical: bool, +) { + let mut widget = Scrollbar::new(max, pos, vertical); widget.style_pos = Style::default(); f.render_widget(widget, r); }