use easy_cast::CastFloat; use std::convert::TryFrom; use tui::{ backend::Backend, buffer::Buffer, layout::{Margin, Rect}, style::{Color, Style}, symbols::{block::FULL, line::DOUBLE_VERTICAL}, widgets::Widget, Frame, }; struct Scrollbar { max: u16, pos: u16, style_bar: Style, style_pos: Style, inside: bool, border: bool, } impl Scrollbar { fn new(max: usize, pos: usize, border: bool, inside: 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(), inside, border, } } } impl Widget for Scrollbar { fn render(self, area: Rect, buf: &mut Buffer) { if area.height <= 2 { return; } if self.max == 0 { return; } let right = if self.inside { area.right().saturating_sub(1) } else { area.right() }; if right <= area.left() { return; }; let (bar_top, bar_height) = { let scrollbar_area = area.inner(&Margin { horizontal: 0, vertical: if self.border { 1 } else { 0 }, }); (scrollbar_area.top(), scrollbar_area.height) }; for y in bar_top..(bar_top + bar_height) { buf.set_string(right, y, DOUBLE_VERTICAL, self.style_bar); } let progress = f32::from(self.pos) / f32::from(self.max); let progress = if progress > 1.0 { 1.0 } else { progress }; let pos = f32::from(bar_height) * progress; let pos: u16 = pos.cast_nearest(); let pos = pos.saturating_sub(1); buf.set_string(right, bar_top + pos, FULL, self.style_pos); } } pub fn draw_scrollbar( f: &mut Frame, r: Rect, max: usize, pos: usize, border: bool, inside: bool, ) { let mut widget = Scrollbar::new(max, pos, border, inside); widget.style_pos = Style::default().fg(Color::Blue); f.render_widget(widget, r); }