diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f57a17e..d0628d27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fully **customizable key bindings** (see [KEY_CONFIG.md](KEY_CONFIG.md)) [[@yanganto](https://github.com/yanganto)] ([#109](https://github.com/extrawurst/gitui/issues/109)) ([#57](https://github.com/extrawurst/gitui/issues/57)) - support scrolling in long commit messages [[@cruessler](https://github.com/cruessler)]([#208](https://github.com/extrawurst/gitui/issues/208)) - copy lines from diffs to clipboard [[@cruessler](https://github.com/cruessler)]([#229](https://github.com/extrawurst/gitui/issues/229)) +- scrollbar in long diffs ([#204](https://github.com/extrawurst/gitui/issues/204)) + +![scrollbar](assets/scrollbar.gif) ### Fixed diff --git a/assets/scrollbar.gif b/assets/scrollbar.gif new file mode 100644 index 00000000..7207bfb4 Binary files /dev/null and b/assets/scrollbar.gif differ diff --git a/src/components/diff.rs b/src/components/diff.rs index e2ea94ec..63519f1d 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -6,7 +6,7 @@ use crate::{ keys::SharedKeyConfig, queue::{Action, InternalEvent, NeedsUpdate, Queue, ResetItem}, strings, try_or_popup, - ui::{calc_scroll_top, style::SharedTheme}, + ui::{self, calc_scroll_top, style::SharedTheme}, }; use asyncgit::{hash, sync, DiffLine, DiffLineType, FileDiff, CWD}; use bytesize::ByteSize; @@ -224,12 +224,18 @@ impl DiffComponent { Ok(()) } + fn lines_count(&self) -> usize { + self.diff + .as_ref() + .map_or(0, |diff| diff.lines.saturating_sub(1)) + } + fn modify_selection( &mut self, direction: Direction, ) -> Result<()> { if let Some(diff) = &self.diff { - let max = diff.lines.saturating_sub(1) as usize; + let max = diff.lines.saturating_sub(1); self.selection.modify(direction, max); } @@ -583,6 +589,15 @@ impl DrawableComponent for DiffComponent { ), r, ); + if self.focused { + ui::draw_scrollbar( + f, + r, + &self.theme, + self.lines_count(), + self.selection.get_end(), + ); + } Ok(()) } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 32502c1a..85486d6e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,8 @@ +mod scrollbar; mod scrolllist; pub mod style; +pub use scrollbar::draw_scrollbar; pub use scrolllist::draw_list; use tui::layout::{Constraint, Direction, Layout, Rect}; diff --git a/src/ui/scrollbar.rs b/src/ui/scrollbar.rs new file mode 100644 index 00000000..ae835c68 --- /dev/null +++ b/src/ui/scrollbar.rs @@ -0,0 +1,77 @@ +use super::style::SharedTheme; +use std::convert::TryFrom; +use tui::{ + backend::Backend, + buffer::Buffer, + layout::{Margin, Rect}, + style::Style, + symbols::{block::FULL, line::THICK_VERTICAL}, + widgets::Widget, + Frame, +}; + +/// +struct Scrollbar { + max: u16, + pos: u16, + style_bar: Style, + style_pos: Style, +} + +impl Scrollbar { + fn new(max: usize, pos: usize) -> 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(), + } + } +} + +impl Widget for Scrollbar { + fn render(self, area: Rect, buf: &mut Buffer) { + let right = area.right().saturating_sub(1); + if right <= area.left() { + return; + }; + + let area = area.inner(&Margin { + horizontal: 0, + vertical: 1, + }); + + if area.height <= 4 { + return; + } + + if area.height > self.max { + return; + } + + for y in area.top()..area.bottom() { + buf.set_string(right, y, THICK_VERTICAL, self.style_bar); + } + + let progress = f32::from(self.pos) / f32::from(self.max); + let pos = f32::from(area.height.saturating_sub(1)) * progress; + //TODO: any better way for this? + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_possible_truncation)] + let pos = pos as u16; + + buf.set_string(right, area.top() + pos, FULL, self.style_pos); + } +} + +pub fn draw_scrollbar( + f: &mut Frame, + r: Rect, + theme: &SharedTheme, + max: usize, + pos: usize, +) { + let mut widget = Scrollbar::new(max, pos); + widget.style_pos = theme.scroll_bar_pos(); + f.render_widget(widget, r) +} diff --git a/src/ui/style.rs b/src/ui/style.rs index 3a33c5ed..26ec6ea0 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -51,6 +51,10 @@ pub struct Theme { } impl Theme { + pub fn scroll_bar_pos(&self) -> Style { + Style::default().fg(self.selection_bg) + } + pub fn block(&self, focus: bool) -> Style { if focus { Style::default()