diff --git a/src/ui/chat/blocks.rs b/src/ui/chat/blocks.rs index 5484282..86075b2 100644 --- a/src/ui/chat/blocks.rs +++ b/src/ui/chat/blocks.rs @@ -1,4 +1,4 @@ -use std::collections::VecDeque; +use std::collections::{vec_deque, VecDeque}; use std::ops::Range; use toss::frame::Frame; @@ -66,6 +66,10 @@ impl Blocks { } } + pub fn iter(&self) -> vec_deque::Iter> { + self.blocks.iter() + } + pub fn offset(&mut self, delta: i32) { self.top_line += delta; self.bottom_line += delta; diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index 52c34cf..b36cc85 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -60,9 +60,7 @@ impl> InnerTreeViewState { KeyCode::Char('j') | KeyCode::Down => self.move_cursor_down().await, KeyCode::Char('g') | KeyCode::Home => self.move_cursor_to_top().await, KeyCode::Char('G') | KeyCode::End => self.move_cursor_to_bottom().await, - KeyCode::Char('y') if event.modifiers == KeyModifiers::CONTROL => { - self.scroll_up(1).await - } + KeyCode::Char('y') if event.modifiers == KeyModifiers::CONTROL => self.scroll_up(1), KeyCode::Char('e') if event.modifiers == KeyModifiers::CONTROL => self.scroll_down(1), _ => return false, } diff --git a/src/ui/chat/tree/cursor.rs b/src/ui/chat/tree/cursor.rs index 25c5aff..48d3fd8 100644 --- a/src/ui/chat/tree/cursor.rs +++ b/src/ui/chat/tree/cursor.rs @@ -224,17 +224,8 @@ impl> InnerTreeViewState { self.make_cursor_visible = true; } - pub async fn scroll_up(&mut self, amount: i32) { + pub fn scroll_up(&mut self, amount: i32) { self.scroll += amount; - - if let Cursor::Bottom = self.cursor { - // Move cursor to bottommost message, if it exists - if let Some(mut id) = self.store.last_tree_id().await { - let tree = self.store.tree(&id).await; - while Self::find_last_child(&tree, &mut id) {} - self.cursor = Cursor::Msg(id); - } - } } pub fn scroll_down(&mut self, amount: i32) { diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index 0098623..efd3f8a 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -308,6 +308,70 @@ impl> InnerTreeViewState { } } + /// Try to obtain a [`Cursor::Msg`] pointing to the block. + fn msg_id(block: &Block>) -> Option { + match &block.id { + BlockId::Msg(id) => Some(id.clone()), + _ => None, + } + } + + fn visible(block: &Block>, height: i32) -> bool { + (1 - block.height..height).contains(&block.top_line) + } + + fn move_cursor_so_it_is_visible( + &mut self, + frame: &mut Frame, + blocks: &TreeBlocks, + ) -> Option { + if !matches!(self.cursor, Cursor::Bottom | Cursor::Msg(_)) { + // In all other cases, there is no need to make the cursor visible + // since scrolling behaves differently enough. + return None; + } + + let height = frame.size().height as i32; + + let new_cursor = if matches!(self.cursor, Cursor::Bottom) { + blocks + .blocks() + .iter() + .rev() + .filter(|b| Self::visible(b, height)) + .find_map(Self::msg_id) + } else { + let block = blocks + .blocks() + .find(&BlockId::from_cursor(&self.cursor)) + .expect("no cursor found"); + + if Self::visible(block, height) { + return None; + } else if block.top_line < 0 { + blocks + .blocks() + .iter() + .filter(|b| Self::visible(b, height)) + .find_map(Self::msg_id) + } else { + blocks + .blocks() + .iter() + .rev() + .filter(|b| Self::visible(b, height)) + .find_map(Self::msg_id) + } + }; + + if let Some(id) = new_cursor { + self.cursor = Cursor::Msg(id.clone()); + Some(id) + } else { + None + } + } + pub async fn relayout(&mut self, frame: &mut Frame) -> TreeBlocks { // The basic idea is this: // @@ -350,9 +414,22 @@ impl> InnerTreeViewState { self.fill_screen_and_clamp_scrolling(frame, &mut blocks) .await; } else { - // self.move_cursor_so_it_is_visible(&mut blocks); // TODO - self.fill_screen_and_clamp_scrolling(frame, &mut blocks) - .await; + let new_cursor_msg_id = self.move_cursor_so_it_is_visible(frame, &blocks); + if let Some(cursor_msg_id) = new_cursor_msg_id { + // Moving the cursor invalidates our current blocks, so we sadly + // have to either perform an expensive operation or redraw the + // entire thing. I'm choosing the latter for now. + + self.last_cursor = self.cursor.clone(); + self.last_cursor_line = self.cursor_line(&blocks); + self.make_cursor_visible = false; + self.scroll = 0; + + let last_cursor_path = self.store.path(&cursor_msg_id).await; + blocks = self.layout_last_cursor_seed(frame, &last_cursor_path).await; + self.fill_screen_and_clamp_scrolling(frame, &mut blocks) + .await; + } } self.last_cursor = self.cursor.clone();