fix scroll being off in branchlist after tab

refactored scroll stuff into new abstraction for reuse
This commit is contained in:
Stephan Dilly 2021-06-05 22:52:46 +02:00
parent 5b07c48234
commit b9c763756a
5 changed files with 118 additions and 26 deletions

1
Cargo.lock generated
View file

@ -484,6 +484,7 @@ dependencies = [
"lazy_static", "lazy_static",
"log", "log",
"pprof", "pprof",
"pretty_assertions",
"rayon-core", "rayon-core",
"ron", "ron",
"scopeguard", "scopeguard",

View file

@ -55,6 +55,9 @@ which = "4.1"
[target.'cfg(not(windows))'.dependencies] [target.'cfg(not(windows))'.dependencies]
pprof = { version = "0.4", features = ["flamegraph"], optional = true } pprof = { version = "0.4", features = ["flamegraph"], optional = true }
[dev-dependencies]
pretty_assertions = "0.7"
[badges] [badges]
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
@ -81,4 +84,4 @@ codegen-units = 1
opt-level = 3 opt-level = 3
[profile.dev] [profile.dev]
split-debuginfo = "unpacked" split-debuginfo = "unpacked"

View file

@ -1,13 +1,14 @@
use super::{ use super::{
visibility_blocking, CommandBlocking, CommandInfo, Component, utils::scroll_vertical::VerticalScroll, visibility_blocking,
DrawableComponent, EventState, CommandBlocking, CommandInfo, Component, DrawableComponent,
EventState,
}; };
use crate::{ use crate::{
components::ScrollType, components::ScrollType,
keys::SharedKeyConfig, keys::SharedKeyConfig,
queue::{Action, InternalEvent, NeedsUpdate, Queue}, queue::{Action, InternalEvent, NeedsUpdate, Queue},
strings, try_or_popup, strings, try_or_popup,
ui::{self, calc_scroll_top, Size}, ui::{self, Size},
}; };
use anyhow::Result; use anyhow::Result;
use asyncgit::{ use asyncgit::{
@ -37,7 +38,7 @@ pub struct BranchListComponent {
local: bool, local: bool,
visible: bool, visible: bool,
selection: u16, selection: u16,
scroll_top: Cell<usize>, scroll: VerticalScroll,
current_height: Cell<u16>, current_height: Cell<u16>,
queue: Queue, queue: Queue,
theme: SharedTheme, theme: SharedTheme,
@ -87,7 +88,6 @@ impl DrawableComponent for BranchListComponent {
.split(area); .split(area);
self.draw_tabs(f, chunks[0]); self.draw_tabs(f, chunks[0]);
self.draw_list(f, chunks[1])?; self.draw_list(f, chunks[1])?;
} }
@ -280,7 +280,7 @@ impl BranchListComponent {
local: true, local: true,
visible: false, visible: false,
selection: 0, selection: 0,
scroll_top: Cell::new(0), scroll: VerticalScroll::new(),
queue, queue,
theme, theme,
key_config, key_config,
@ -381,9 +381,13 @@ impl BranchListComponent {
width_available: u16, width_available: u16,
height: usize, height: usize,
) -> Text { ) -> Text {
const UPSTREAM_SYMBOL: char = '\u{2191}';
const HEAD_SYMBOL: char = '*';
const EMPTY_SYMBOL: char = ' ';
const THREE_DOTS: &str = "...";
const COMMIT_HASH_LENGTH: usize = 8; const COMMIT_HASH_LENGTH: usize = 8;
const IS_HEAD_STAR_LENGTH: usize = 3; // "* " const IS_HEAD_STAR_LENGTH: usize = 3; // "* "
const THREE_DOTS_LENGTH: usize = 3; // "..." const THREE_DOTS_LENGTH: usize = THREE_DOTS.len(); // "..."
let branch_name_length: usize = let branch_name_length: usize =
width_available as usize * 40 / 100; width_available as usize * 40 / 100;
@ -398,7 +402,7 @@ impl BranchListComponent {
for (i, displaybranch) in self for (i, displaybranch) in self
.branches .branches
.iter() .iter()
.skip(self.scroll_top.get()) .skip(self.scroll.get())
.take(height) .take(height)
.enumerate() .enumerate()
{ {
@ -409,7 +413,7 @@ impl BranchListComponent {
commit_message_length commit_message_length
.saturating_sub(THREE_DOTS_LENGTH), .saturating_sub(THREE_DOTS_LENGTH),
); );
commit_message += "..."; commit_message += THREE_DOTS;
} }
let mut branch_name = displaybranch.name.clone(); let mut branch_name = displaybranch.name.clone();
@ -423,25 +427,26 @@ impl BranchListComponent {
) )
.0 .0
.to_string(); .to_string();
branch_name += "..."; branch_name += THREE_DOTS;
} }
let selected = let selected =
self.selection as usize - self.scroll_top.get() == i; (self.selection as usize - self.scroll.get()) == i;
let is_head = displaybranch let is_head = displaybranch
.local_details() .local_details()
.map(|details| details.is_head) .map(|details| details.is_head)
.unwrap_or_default(); .unwrap_or_default();
let is_head_str = if is_head { "*" } else { " " }; let is_head_str =
if is_head { HEAD_SYMBOL } else { EMPTY_SYMBOL };
let has_upstream_str = if displaybranch let has_upstream_str = if displaybranch
.local_details() .local_details()
.map(|details| details.has_upstream) .map(|details| details.has_upstream)
.unwrap_or_default() .unwrap_or_default()
{ {
"\u{2191}" UPSTREAM_SYMBOL
} else { } else {
" " EMPTY_SYMBOL
}; };
let span_prefix = Span::styled( let span_prefix = Span::styled(
@ -537,11 +542,11 @@ impl BranchListComponent {
let height_in_lines = r.height as usize; let height_in_lines = r.height as usize;
self.current_height.set(height_in_lines.try_into()?); self.current_height.set(height_in_lines.try_into()?);
self.scroll_top.set(calc_scroll_top( self.scroll.update(
self.scroll_top.get(),
height_in_lines,
self.selection as usize, self.selection as usize,
)); self.branches.len(),
height_in_lines,
);
f.render_widget( f.render_widget(
Paragraph::new(self.get_text( Paragraph::new(self.get_text(
@ -558,13 +563,7 @@ impl BranchListComponent {
r.height += 2; r.height += 2;
r.y = r.y.saturating_sub(1); r.y = r.y.saturating_sub(1);
ui::draw_scrollbar( self.scroll.draw(f, r, &self.theme);
f,
r,
&self.theme,
self.branches.len().saturating_sub(height_in_lines),
self.scroll_top.get(),
);
Ok(()) Ok(())
} }

View file

@ -3,6 +3,7 @@ use unicode_width::UnicodeWidthStr;
pub mod filetree; pub mod filetree;
pub mod logitems; pub mod logitems;
pub mod scroll_vertical;
pub mod statustree; pub mod statustree;
/// macro to simplify running code that might return Err. /// macro to simplify running code that might return Err.

View file

@ -0,0 +1,88 @@
use std::cell::Cell;
use tui::{backend::Backend, layout::Rect, Frame};
use crate::ui::{draw_scrollbar, style::SharedTheme};
pub struct VerticalScroll {
top: Cell<usize>,
max_top: Cell<usize>,
}
impl VerticalScroll {
pub const fn new() -> Self {
Self {
top: Cell::new(0),
max_top: Cell::new(0),
}
}
pub fn get(&self) -> usize {
self.top.get()
}
pub fn update(
&self,
selection: usize,
selection_max: usize,
visual_height: usize,
) -> usize {
let new_top = calc_scroll_top(
self.get(),
visual_height,
selection,
selection_max,
);
self.top.set(new_top);
let new_max = selection_max.saturating_sub(visual_height);
self.max_top.set(new_max);
new_top
}
pub fn draw<B: Backend>(
&self,
f: &mut Frame<B>,
r: Rect,
theme: &SharedTheme,
) {
draw_scrollbar(
f,
r,
theme,
self.max_top.get(),
self.top.get(),
);
}
}
const fn calc_scroll_top(
current_top: usize,
height_in_lines: usize,
selection: usize,
selection_max: usize,
) -> usize {
if selection_max < height_in_lines {
return 0;
}
if current_top + height_in_lines <= selection {
selection.saturating_sub(height_in_lines) + 1
} else if current_top > selection {
selection
} else {
current_top
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_scroll_no_scroll_to_top() {
assert_eq!(calc_scroll_top(1, 10, 4, 4), 0);
}
}