diff --git a/benches/criterion.rs b/benches/criterion.rs index 04d31a4..f9bddee 100644 --- a/benches/criterion.rs +++ b/benches/criterion.rs @@ -121,7 +121,7 @@ fn draw_benchmark(c: &mut Criterion) { c.bench_function("draw on terminal", |b| { b.iter(|| { - terminal.draw(|f| ui::draw(f, &app, &lua)).unwrap(); + terminal.draw(|f| ui::draw(f, &mut app, &lua)).unwrap(); }) }); diff --git a/src/app.rs b/src/app.rs index 49f3b33..3a32cee 100644 --- a/src/app.rs +++ b/src/app.rs @@ -752,7 +752,10 @@ impl App { self.explorer_config.clone(), self.pwd.clone().into(), focus.as_ref().map(PathBuf::from), - self.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0), + self.directory_buffer + .as_ref() + .map(|d| d.scroll_state.current_focus) + .unwrap_or(0), ) { Ok(dir) => self.set_directory(dir), Err(e) => { @@ -791,7 +794,7 @@ impl App { } } - dir.focus = 0; + dir.scroll_state.set_focus(0); if save_history { if let Some(n) = self.focused_node() { @@ -809,7 +812,7 @@ impl App { history = history.push(n.absolute_path.clone()); } - dir.focus = dir.total.saturating_sub(1); + dir.scroll_state.set_focus(dir.total.saturating_sub(1)); if let Some(n) = dir.focused_node() { self.history = history.push(n.absolute_path.clone()); @@ -822,14 +825,15 @@ impl App { let bounded = self.config.general.enforce_bounded_index_navigation; if let Some(dir) = self.directory_buffer_mut() { - dir.focus = if dir.focus == 0 { + if dir.scroll_state.current_focus == 0 { if bounded { - dir.focus + dir.scroll_state.set_focus(dir.scroll_state.current_focus); } else { - dir.total.saturating_sub(1) + dir.scroll_state.set_focus(dir.total.saturating_sub(1)); } } else { - dir.focus.saturating_sub(1) + dir.scroll_state + .set_focus(dir.scroll_state.current_focus.saturating_sub(1)); }; }; Ok(self) @@ -882,7 +886,8 @@ impl App { history = history.push(n.absolute_path.clone()); } - dir.focus = dir.focus.saturating_sub(index); + dir.scroll_state + .set_focus(dir.scroll_state.current_focus.saturating_sub(index)); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); } @@ -907,14 +912,15 @@ impl App { let bounded = self.config.general.enforce_bounded_index_navigation; if let Some(dir) = self.directory_buffer_mut() { - dir.focus = if (dir.focus + 1) == dir.total { + if (dir.scroll_state.current_focus + 1) == dir.total { if bounded { - dir.focus + dir.scroll_state.set_focus(dir.scroll_state.current_focus); } else { - 0 + dir.scroll_state.set_focus(0); } } else { - dir.focus + 1 + dir.scroll_state + .set_focus(dir.scroll_state.current_focus + 1); } }; Ok(self) @@ -967,10 +973,12 @@ impl App { history = history.push(n.absolute_path.clone()); } - dir.focus = dir - .focus - .saturating_add(index) - .min(dir.total.saturating_sub(1)); + dir.scroll_state.set_focus( + dir.scroll_state + .current_focus + .saturating_add(index) + .min(dir.total.saturating_sub(1)), + ); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); @@ -1238,7 +1246,8 @@ impl App { fn focus_by_index(mut self, index: usize) -> Result { let history = self.history.clone(); if let Some(dir) = self.directory_buffer_mut() { - dir.focus = index.min(dir.total.saturating_sub(1)); + dir.scroll_state + .set_focus(index.min(dir.total.saturating_sub(1))); if let Some(n) = self.focused_node() { self.history = history.push(n.absolute_path.clone()); } @@ -1275,7 +1284,7 @@ impl App { history = history.push(n.absolute_path.clone()); } } - dir_buf.focus = focus; + dir_buf.scroll_state.set_focus(focus); if save_history { if let Some(n) = dir_buf.focused_node() { self.history = history.push(n.absolute_path.clone()); diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs index c6e755d..cf6a5c1 100644 --- a/src/directory_buffer.rs +++ b/src/directory_buffer.rs @@ -2,31 +2,93 @@ use crate::node::Node; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct ScrollState { + pub current_focus: usize, + pub last_focus: Option, + pub skipped_rows: usize, +} + +impl ScrollState { + /* The number of visible next lines when scrolling towards either ends of the view port */ + pub const PREVIEW_CUSHION: usize = 3; + + pub fn set_focus(&mut self, current_focus: usize) { + self.last_focus = Some(self.current_focus); + self.current_focus = current_focus; + } + + pub fn calc_skipped_rows(&mut self, height: usize, total: usize) -> usize { + let current_focus = self.current_focus; + let last_focus = self.last_focus; + let first_visible_row = self.skipped_rows; + let start_cushion_row = first_visible_row + ScrollState::PREVIEW_CUSHION; + let end_cushion_row = (first_visible_row + height) + .saturating_sub(ScrollState::PREVIEW_CUSHION + 1); + + let vim_scrolling_enabled = true; + + if !vim_scrolling_enabled { + height * (self.current_focus / height.max(1)) + } else if last_focus == None { + // Just entered the directory + 0 + } else if current_focus == 0 { + 0 + } else if current_focus == total.saturating_sub(1) { + total.saturating_sub(height) + } else if current_focus > last_focus.unwrap() { + // Scrolling down + if current_focus <= end_cushion_row { + first_visible_row + } else if total <= (current_focus + ScrollState::PREVIEW_CUSHION) { + first_visible_row + } else { + (self.current_focus + ScrollState::PREVIEW_CUSHION + 1) + .saturating_sub(height) + } + } else { + // Scrolling up + if current_focus >= start_cushion_row { + first_visible_row + } else if current_focus <= ScrollState::PREVIEW_CUSHION { + 0 + } else { + current_focus.saturating_sub(ScrollState::PREVIEW_CUSHION) + } + } + } +} + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct DirectoryBuffer { pub parent: String, pub nodes: Vec, pub total: usize, - pub focus: usize, + pub scroll_state: ScrollState, #[serde(skip, default = "now")] pub explored_at: OffsetDateTime, } impl DirectoryBuffer { - pub fn new(parent: String, nodes: Vec, focus: usize) -> Self { + pub fn new(parent: String, nodes: Vec, current_focus: usize) -> Self { let total = nodes.len(); Self { parent, nodes, total, - focus, + scroll_state: ScrollState { + current_focus, + last_focus: None, + skipped_rows: 0, + }, explored_at: now(), } } pub fn focused_node(&self) -> Option<&Node> { - self.nodes.get(self.focus) + self.nodes.get(self.scroll_state.current_focus) } } diff --git a/src/runner.rs b/src/runner.rs index 4b80274..7c15bfc 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -89,7 +89,7 @@ fn call( let focus_index = app .directory_buffer .as_ref() - .map(|d| d.focus) + .map(|d| d.scroll_state.current_focus) .unwrap_or_default() .to_string(); @@ -279,7 +279,10 @@ impl Runner { app.explorer_config.clone(), app.pwd.clone().into(), self.focused_path, - app.directory_buffer.as_ref().map(|d| d.focus).unwrap_or(0), + app.directory_buffer + .as_ref() + .map(|d| d.scroll_state.current_focus) + .unwrap_or(0), tx_msg_in.clone(), ); tx_pwd_watcher.send(app.pwd.clone())?; @@ -430,7 +433,7 @@ impl Runner { .map(|n| n.relative_path.clone().into()), app.directory_buffer .as_ref() - .map(|d| d.focus) + .map(|d| d.scroll_state.current_focus) .unwrap_or(0), tx_msg_in.clone(), ); @@ -445,7 +448,7 @@ impl Runner { .map(|n| n.relative_path.clone().into()), app.directory_buffer .as_ref() - .map(|d| d.focus) + .map(|d| d.scroll_state.current_focus) .unwrap_or(0), tx_msg_in.clone(), ); @@ -493,7 +496,7 @@ impl Runner { } // UI - terminal.draw(|f| ui::draw(f, &app, &lua))?; + terminal.draw(|f| ui::draw(f, &mut app, &lua))?; } EnableMouse => { diff --git a/src/ui.rs b/src/ui.rs index 0d3dda0..838b1ef 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -722,7 +722,7 @@ fn draw_table( f: &mut Frame, screen_size: TuiRect, layout_size: TuiRect, - app: &app::App, + app: &mut app::App, lua: &Lua, ) { let panel_config = &app.config.general.panel_ui; @@ -735,15 +735,16 @@ fn draw_table( let rows = app .directory_buffer - .as_ref() + .as_mut() .map(|dir| { + dir.scroll_state.skipped_rows = dir.scroll_state.calc_skipped_rows(height, dir.total); dir.nodes .iter() .enumerate() - .skip(height * (dir.focus / height.max(1))) + .skip(dir.scroll_state.skipped_rows) .take(height) .map(|(index, node)| { - let is_focused = dir.focus == index; + let is_focused = dir.scroll_state.current_focus == index; let is_selected = app .selection @@ -772,9 +773,13 @@ fn draw_table( let node_type = app_config.node_types.get(node); let (relative_index, is_before_focus, is_after_focus) = - match dir.focus.cmp(&index) { - Ordering::Greater => (dir.focus - index, true, false), - Ordering::Less => (index - dir.focus, false, true), + match dir.scroll_state.current_focus.cmp(&index) { + Ordering::Greater => { + (dir.scroll_state.current_focus - index, true, false) + } + Ordering::Less => { + (index - dir.scroll_state.current_focus, false, true) + } Ordering::Equal => (0, false, false), }; @@ -1284,7 +1289,7 @@ pub fn draw_dynamic( f: &mut Frame, screen_size: TuiRect, layout_size: TuiRect, - app: &app::App, + app: &mut app::App, func: &str, lua: &Lua, ) { @@ -1308,7 +1313,7 @@ pub fn draw_static( f: &mut Frame, screen_size: TuiRect, layout_size: TuiRect, - app: &app::App, + app: &mut app::App, panel: CustomPanel, _lua: &Lua, ) { @@ -1402,7 +1407,7 @@ pub fn draw_layout( f: &mut Frame, screen_size: TuiRect, layout_size: TuiRect, - app: &app::App, + app: &mut app::App, lua: &Lua, ) { match layout { @@ -1493,7 +1498,7 @@ pub fn draw_layout( } } -pub fn draw(f: &mut Frame, app: &app::App, lua: &Lua) { +pub fn draw(f: &mut Frame, app: &mut app::App, lua: &Lua) { let screen_size = f.size(); let layout = app.mode.layout.as_ref().unwrap_or(&app.layout).to_owned();