diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cd53ad..3c4995a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,10 +17,11 @@ Procedure when bumping the version number: ### Added - New messages are now marked as unseen - Sub-trees can now be folded +- More readline-esque editor key bindings - Key bindings to move to prev/next sibling +- Key binding to center cursor on screen ### Changed -- Improved editor key bindings - Slowed down room history download speed ### Fixed diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index e14a7c7..67c7ed2 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -30,6 +30,7 @@ use super::{ChatMsg, Reaction}; enum Correction { MakeCursorVisible, MoveCursorToVisibleArea, + CenterCursor, } struct InnerTreeViewState> { @@ -75,6 +76,7 @@ impl> InnerTreeViewState { bindings.binding("ctrl+y/e", "scroll up/down a line"); bindings.binding("ctrl+u/d", "scroll up/down half a screen"); bindings.binding("ctrl+b/f", "scroll up/down one screen"); + bindings.binding("z", "center cursor on screen"); } async fn handle_movement_key_event(&mut self, frame: &mut Frame, event: KeyEvent) -> bool { @@ -97,6 +99,7 @@ impl> InnerTreeViewState { key!(Ctrl + 'd') => self.scroll_down((chat_height / 2).into()), key!(Ctrl + 'b') => self.scroll_up(chat_height.saturating_sub(1).into()), key!(Ctrl + 'f') => self.scroll_down(chat_height.saturating_sub(1).into()), + key!('z') => self.center_cursor(), _ => return false, } diff --git a/src/ui/chat/tree/cursor.rs b/src/ui/chat/tree/cursor.rs index fafa74c..aba8bc3 100644 --- a/src/ui/chat/tree/cursor.rs +++ b/src/ui/chat/tree/cursor.rs @@ -379,6 +379,10 @@ impl> InnerTreeViewState { self.correction = Some(Correction::MoveCursorToVisibleArea); } + pub fn center_cursor(&mut self) { + self.correction = Some(Correction::CenterCursor); + } + pub async fn parent_for_normal_reply(&self) -> Option> { match &self.cursor { Cursor::Bottom => Some(None), diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index 0122dcf..7e41f00 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -368,6 +368,33 @@ impl> InnerTreeViewState { } } + fn scroll_so_cursor_is_centered(&self, frame: &mut Frame, blocks: &mut TreeBlocks) { + if matches!(self.cursor, Cursor::Bottom) { + return; // Cursor is locked to bottom + } + + let block = blocks + .blocks() + .find(&BlockId::from_cursor(&self.cursor)) + .expect("no cursor found"); + + let height = frame.size().height as i32; + let scrolloff = scrolloff(height); + + let min_line = -block.focus.start + scrolloff; + let max_line = height - block.focus.end - scrolloff; + + // If the message is higher than the available space, the top of the + // message should always be visible. I'm not using top_line.clamp(...) + // because the order of the min and max matters. + let top_line = block.top_line; + let new_top_line = (height - block.height) / 2; + let new_top_line = new_top_line.min(max_line).max(min_line); + if new_top_line != top_line { + blocks.blocks_mut().offset(new_top_line - top_line); + } + } + /// Try to obtain a [`Cursor::Msg`] pointing to the block. fn msg_id(block: &Block>) -> Option { match &block.id { @@ -518,6 +545,11 @@ impl> InnerTreeViewState { .await; } } + Some(Correction::CenterCursor) => { + self.scroll_so_cursor_is_centered(frame, &mut blocks); + self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks) + .await; + } None => {} }