diff --git a/cove-tui/src/chat/tree.rs b/cove-tui/src/chat/tree.rs index d6ec8bd..c85c177 100644 --- a/cove-tui/src/chat/tree.rs +++ b/cove-tui/src/chat/tree.rs @@ -1,4 +1,5 @@ mod blocks; +mod cursor; mod layout; mod render; mod util; @@ -26,48 +27,6 @@ impl TreeView { } } - async fn move_to_prev_msg>( - &mut self, - store: &mut S, - room: &str, - cursor: &mut Option>, - ) { - let tree = if let Some(cursor) = cursor { - let path = store.path(room, &cursor.id).await; - let tree = store.tree(room, path.first()).await; - if let Some(prev_sibling) = tree.prev_sibling(&cursor.id) { - cursor.id = tree.last_child(prev_sibling.clone()); - return; - } else if let Some(parent) = tree.parent(&cursor.id) { - cursor.id = parent; - return; - } else { - store.prev_tree(room, path.first()).await - } - } else { - store.last_tree(room).await - }; - - if let Some(tree) = tree { - let tree = store.tree(room, &tree).await; - let cursor_id = tree.last_child(tree.root().clone()); - if let Some(cursor) = cursor { - cursor.id = cursor_id; - } else { - *cursor = Some(Cursor { - id: cursor_id, - proportion: 1.0, - }); - } - } - } - - async fn center_cursor(&mut self, cursor: &mut Option>) { - if let Some(cursor) = cursor { - cursor.proportion = 0.5; - } - } - pub async fn handle_key_event>( &mut self, store: &mut S, @@ -93,7 +52,9 @@ impl TreeView { pos: Pos, size: Size, ) { - let blocks = self.layout_blocks(room, store, cursor, frame, size).await; + let blocks = self + .layout_blocks(room, store, cursor.as_ref(), frame, size) + .await; Self::render_blocks(frame, pos, size, &blocks); } } diff --git a/cove-tui/src/chat/tree/blocks.rs b/cove-tui/src/chat/tree/blocks.rs index a479947..c156909 100644 --- a/cove-tui/src/chat/tree/blocks.rs +++ b/cove-tui/src/chat/tree/blocks.rs @@ -112,12 +112,12 @@ impl Blocks { if &block.id == id { block.cursor = true; if cursor.is_some() { - panic!("more than one cursor in layout"); + panic!("more than one cursor in blocks"); } cursor = Some(i); } } - cursor.expect("no cursor in layout") + cursor.expect("no cursor in blocks") } pub fn calculate_offsets_with_cursor(&mut self, cursor: &Cursor, height: u16) { @@ -165,4 +165,8 @@ impl Blocks { self.push_back(block); } } + + pub fn find(&self, id: &I) -> Option<&Block> { + self.blocks.iter().find(|b| &b.id == id) + } } diff --git a/cove-tui/src/chat/tree/cursor.rs b/cove-tui/src/chat/tree/cursor.rs new file mode 100644 index 0000000..ad0dd3e --- /dev/null +++ b/cove-tui/src/chat/tree/cursor.rs @@ -0,0 +1,134 @@ +//! Moving the cursor around. + +use toss::frame::{Frame, Size}; + +use crate::chat::Cursor; +use crate::store::{Msg, MsgStore}; + +use super::blocks::Blocks; +use super::{util, TreeView}; + +impl TreeView { + async fn correct_cursor_offset>( + &mut self, + room: &str, + store: &S, + frame: &mut Frame, + size: Size, + old_blocks: &Blocks, + old_cursor_id: &M::Id, + cursor: &mut Cursor, + ) { + if let Some(block) = old_blocks.find(&cursor.id) { + // The cursor is still visible in the old blocks, so we just need to + // adjust the proportion such that the blocks stay still. + cursor.proportion = util::line_to_proportion(size.height, block.line); + } else { + // The cursor is not visible any more. However, we can estimate + // whether it is above or below the previous cursor position by + // lexicographically comparing both positions' paths. + let old_path = store.path(room, old_cursor_id).await; + let new_path = store.path(room, &cursor.id).await; + if new_path < old_path { + // Because we moved upwards, the cursor should appear at the top + // of the screen. + cursor.proportion = 0.0; + } else { + // Because we moved downwards, the cursor should appear at the + // bottom of the screen. + cursor.proportion = 1.0; + } + } + + // The cursor should be visible in its entirety on the screen now. If it + // isn't, we should scroll the screen such that the cursor becomes fully + // visible again. To do this, we'll need to re-layout because the cursor + // could've moved anywhere. + let blocks = self + .layout_blocks(room, store, Some(cursor), frame, size) + .await; + let cursor_block = blocks.find(&cursor.id).expect("cursor must be in blocks"); + // First, ensure the cursor's last line is not below the bottom of the + // screen. Then, ensure its top line is not above the top of the screen. + // If the cursor is higher than the screen, the user should still see + // the top of the cursor so they can start reading its contents. + let max_line = size.height as i32 - cursor_block.height; + let cursor_line = cursor_block.line.min(max_line).max(0); + cursor.proportion = util::line_to_proportion(size.height, cursor_line); + + // There is no need to ensure the screen is not scrolled too far up or + // down. The messages in `blocks` are already scrolled correctly and + // this function will not scroll the wrong way. If the cursor moves too + // far up, the screen will only scroll down, not further up. The same + // goes for the other direction. + } + + pub async fn move_up() { + todo!() + } + + pub async fn move_down() { + todo!() + } + + pub async fn move_up_sibling() { + todo!() + } + + pub async fn move_down_sibling() { + todo!() + } + + pub async fn move_older() { + todo!() + } + + pub async fn move_newer() { + todo!() + } + + // TODO move_older_unseen + // TODO move_newer_unseen + + pub async fn move_to_prev_msg>( + &mut self, + store: &mut S, + room: &str, + cursor: &mut Option>, + ) { + let tree = if let Some(cursor) = cursor { + let path = store.path(room, &cursor.id).await; + let tree = store.tree(room, path.first()).await; + if let Some(prev_sibling) = tree.prev_sibling(&cursor.id) { + cursor.id = tree.last_child(prev_sibling.clone()); + return; + } else if let Some(parent) = tree.parent(&cursor.id) { + cursor.id = parent; + return; + } else { + store.prev_tree(room, path.first()).await + } + } else { + store.last_tree(room).await + }; + + if let Some(tree) = tree { + let tree = store.tree(room, &tree).await; + let cursor_id = tree.last_child(tree.root().clone()); + if let Some(cursor) = cursor { + cursor.id = cursor_id; + } else { + *cursor = Some(Cursor { + id: cursor_id, + proportion: 1.0, + }); + } + } + } + + pub async fn center_cursor(&mut self, cursor: &mut Option>) { + if let Some(cursor) = cursor { + cursor.proportion = 0.5; + } + } +} diff --git a/cove-tui/src/chat/tree/layout.rs b/cove-tui/src/chat/tree/layout.rs index 7cc1cf7..8f9eb30 100644 --- a/cove-tui/src/chat/tree/layout.rs +++ b/cove-tui/src/chat/tree/layout.rs @@ -91,11 +91,12 @@ impl TreeView { } } + // TODO Split up based on cursor presence pub async fn layout_blocks>( &mut self, room: &str, store: &S, - cursor: &Option>, + cursor: Option<&Cursor>, frame: &mut Frame, size: Size, ) -> Blocks { @@ -111,6 +112,7 @@ impl TreeView { layout.calculate_offsets_with_cursor(cursor, size.height); // Expand upwards and downwards + // TODO Ensure that blocks are scrolled correctly // TODO Don't do this if there is a focus if let Some(prev_tree) = store.prev_tree(room, cursor_tree_id).await { Self::expand_blocks_up(room, store, frame, size, &mut layout, prev_tree).await; diff --git a/cove-tui/src/store.rs b/cove-tui/src/store.rs index b5aea3e..ffbaad8 100644 --- a/cove-tui/src/store.rs +++ b/cove-tui/src/store.rs @@ -17,6 +17,7 @@ pub trait Msg { fn content(&self) -> String; } +#[derive(PartialEq, Eq, PartialOrd, Ord)] pub struct Path(Vec); impl Path {