From 48943f23162502d55e02a9cc47888cde5bc37da3 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 20 Jul 2022 14:07:02 +0200 Subject: [PATCH] Port most of cursor movement logic --- src/ui/chat.rs | 17 --- src/ui/chat/tree.rs | 47 +------ src/ui/chat/tree/cursor.rs | 281 ++++++++++++------------------------- src/ui/chat/tree/layout.rs | 3 +- 4 files changed, 94 insertions(+), 254 deletions(-) diff --git a/src/ui/chat.rs b/src/ui/chat.rs index f900ff6..1dffad4 100644 --- a/src/ui/chat.rs +++ b/src/ui/chat.rs @@ -19,23 +19,6 @@ pub enum Mode { // Flat, } -pub struct Cursor { - id: I, - /// Where on the screen the cursor is visible (`0.0` = first line, `1.0` = - /// last line). - proportion: f32, -} - -impl Cursor { - /// Create a new cursor with arbitrary proportion. - pub fn new(id: I) -> Self { - Self { - id, - proportion: 0.0, - } - } -} - pub struct ChatState> { store: S, mode: Mode, diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index c026a42..9b2acee 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -1,6 +1,6 @@ // mod action; mod blocks; -// mod cursor; +mod cursor; mod layout; mod render; mod util; @@ -14,54 +14,13 @@ use toss::frame::{Frame, Size}; use crate::store::{Msg, MsgStore}; use crate::ui::widgets::Widget; -use self::blocks::{Block, BlockBody, Blocks, MarkerBlock}; +use self::blocks::Blocks; +use self::cursor::Cursor; /////////// // State // /////////// -/// Position of a cursor that is displayed as the last child of its parent -/// message, or last thread if it has no parent. -#[derive(Debug, Clone, Copy)] -struct LastChild { - coming_from: Option, - after: Option, -} - -#[derive(Debug, Clone, Copy)] -enum Cursor { - /// No cursor visible because it is at the bottom of the chat history. - /// - /// See also [`Anchor::Bottom`]. - Bottom, - /// The cursor points to a message. - Msg(I), - /// The cursor has turned into an editor because we're composing a new - /// message. - Compose(LastChild), - /// A placeholder message is being displayed for a message that was just - /// sent by the user. - /// - /// Will be replaced by a [`Cursor::Msg`] as soon as the server replies to - /// the send command with the sent message. - Placeholder(LastChild), -} - -impl Cursor { - fn matches_block(&self, block: &Block) -> bool { - match self { - Self::Bottom => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), - Self::Msg(id) => matches!(&block.body, BlockBody::Msg(msg) if msg.id == *id), - Self::Compose(lc) | Self::Placeholder(lc) => match &lc.after { - Some(bid) => { - matches!(&block.body, BlockBody::Marker(MarkerBlock::After(aid)) if aid == bid) - } - None => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), - }, - } - } -} - struct InnerTreeViewState> { store: S, last_blocks: Blocks, diff --git a/src/ui/chat/tree/cursor.rs b/src/ui/chat/tree/cursor.rs index e0fe81d..4b7dd66 100644 --- a/src/ui/chat/tree/cursor.rs +++ b/src/ui/chat/tree/cursor.rs @@ -1,71 +1,53 @@ //! Moving the cursor around. -use toss::frame::{Frame, Size}; - use crate::store::{Msg, MsgStore, Tree}; -use super::blocks::Blocks; -use super::{util, Cursor, TreeView}; +use super::blocks::{Block, BlockBody, MarkerBlock}; +use super::InnerTreeViewState; -impl TreeView { - #[allow(clippy::too_many_arguments)] - async fn correct_cursor_offset>( - &mut self, - store: &S, - frame: &mut Frame, - size: Size, - old_blocks: &Blocks, - old_cursor_id: &Option, - 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 if let Some(old_cursor_id) = old_cursor_id { - // 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(old_cursor_id).await; - let new_path = store.path(&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; - } - } else { - // We were scrolled all the way to the bottom, so the cursor must - // have been offscreen somewhere above. - cursor.proportion = 0.0; +/// Position of a cursor that is displayed as the last child of its parent +/// message, or last thread if it has no parent. +#[derive(Debug, Clone, Copy)] +pub struct LastChild { + pub coming_from: Option, + pub after: Option, +} + +#[derive(Debug, Clone, Copy)] +pub enum Cursor { + /// No cursor visible because it is at the bottom of the chat history. + /// + /// See also [`Anchor::Bottom`]. + Bottom, + /// The cursor points to a message. + Msg(I), + /// The cursor has turned into an editor because we're composing a new + /// message. + Compose(LastChild), + /// A placeholder message is being displayed for a message that was just + /// sent by the user. + /// + /// Will be replaced by a [`Cursor::Msg`] as soon as the server replies to + /// the send command with the sent message. + Placeholder(LastChild), +} + +impl Cursor { + pub fn matches_block(&self, block: &Block) -> bool { + match self { + Self::Bottom => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), + Self::Msg(id) => matches!(&block.body, BlockBody::Msg(msg) if msg.id == *id), + Self::Compose(lc) | Self::Placeholder(lc) => match &lc.after { + Some(bid) => { + matches!(&block.body, BlockBody::Marker(MarkerBlock::After(aid)) if aid == bid) + } + None => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), + }, } - - // The cursor should be visible in its entirety on the screen now. If it - // isn't, we need to 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(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 has more lines than the screen, the user should still - // see the top of the cursor so they can start reading its contents. - let min_line = 0; - let max_line = size.height as i32 - cursor_block.height; - // Not using clamp because it is possible that max_line < min_line - let cursor_line = cursor_block.line.min(max_line).max(min_line); - 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. } +} +impl> InnerTreeViewState { fn find_parent(tree: &Tree, id: &mut M::Id) -> bool { if let Some(parent) = tree.parent(id) { *id = parent; @@ -96,20 +78,15 @@ impl TreeView { /// Move to the previous sibling, or don't move if this is not possible. /// /// Always stays at the same level of indentation. - async fn find_prev_sibling>( - &self, - store: &S, - tree: &mut Tree, - id: &mut M::Id, - ) -> bool { + async fn find_prev_sibling(&self, tree: &mut Tree, id: &mut M::Id) -> bool { if let Some(prev_sibling) = tree.prev_sibling(id) { *id = prev_sibling; true } else if tree.parent(id).is_none() { // We're at the root of our tree, so we need to move to the root of // the previous tree. - if let Some(prev_tree_id) = store.prev_tree(tree.root()).await { - *tree = store.tree(&prev_tree_id).await; + if let Some(prev_tree_id) = self.store.prev_tree(tree.root()).await { + *tree = self.store.tree(&prev_tree_id).await; *id = prev_tree_id; true } else { @@ -123,20 +100,15 @@ impl TreeView { /// Move to the next sibling, or don't move if this is not possible. /// /// Always stays at the same level of indentation. - async fn find_next_sibling>( - &self, - store: &S, - tree: &mut Tree, - id: &mut M::Id, - ) -> bool { + async fn find_next_sibling(&self, tree: &mut Tree, id: &mut M::Id) -> bool { if let Some(next_sibling) = tree.next_sibling(id) { *id = next_sibling; true } else if tree.parent(id).is_none() { // We're at the root of our tree, so we need to move to the root of // the next tree. - if let Some(next_tree_id) = store.next_tree(tree.root()).await { - *tree = store.tree(&next_tree_id).await; + if let Some(next_tree_id) = self.store.next_tree(tree.root()).await { + *tree = self.store.tree(&next_tree_id).await; *id = next_tree_id; true } else { @@ -148,15 +120,10 @@ impl TreeView { } /// Move to the previous message, or don't move if this is not possible. - async fn find_prev_msg>( - &self, - store: &S, - tree: &mut Tree, - id: &mut M::Id, - ) -> bool { + async fn find_prev_msg(&self, tree: &mut Tree, id: &mut M::Id) -> bool { // Move to previous sibling, then to its last child // If not possible, move to parent - if self.find_prev_sibling(store, tree, id).await { + if self.find_prev_sibling(tree, id).await { while Self::find_last_child(tree, id) {} true } else { @@ -165,17 +132,12 @@ impl TreeView { } /// Move to the next message, or don't move if this is not possible. - async fn find_next_msg>( - &self, - store: &S, - tree: &mut Tree, - id: &mut M::Id, - ) -> bool { + async fn find_next_msg(&self, tree: &mut Tree, id: &mut M::Id) -> bool { if Self::find_first_child(tree, id) { return true; } - if self.find_next_sibling(store, tree, id).await { + if self.find_next_sibling(tree, id).await { return true; } @@ -183,7 +145,7 @@ impl TreeView { // can be found. let mut tmp_id = id.clone(); while Self::find_parent(tree, &mut tmp_id) { - if self.find_next_sibling(store, tree, &mut tmp_id).await { + if self.find_next_sibling(tree, &mut tmp_id).await { *id = tmp_id; return true; } @@ -192,65 +154,47 @@ impl TreeView { false } - pub async fn move_up>( - &mut self, - store: &S, - cursor: &mut Option>, - frame: &mut Frame, - size: Size, - ) { - let old_blocks = self - .layout_blocks(store, cursor.as_ref(), frame, size) - .await; - let old_cursor_id = cursor.as_ref().map(|c| c.id.clone()); - - if let Some(cursor) = cursor { - // We have a cursor to move around - let path = store.path(&cursor.id).await; - let mut tree = store.tree(path.first()).await; - self.find_prev_msg(store, &mut tree, &mut cursor.id).await; - } else if let Some(last_tree) = store.last_tree().await { - // We need to select the last message of the last tree - let tree = store.tree(&last_tree).await; - let mut id = last_tree; - while Self::find_last_child(&tree, &mut id) {} - *cursor = Some(Cursor::new(id)); - } - // If neither condition holds, we can't set a cursor because there's no - // message to move to. - - if let Some(cursor) = cursor { - self.correct_cursor_offset(store, frame, size, &old_blocks, &old_cursor_id, cursor) - .await; + pub async fn move_up(&mut self, cursor: &mut Cursor) { + match cursor { + Cursor::Bottom => { + if let Some(last_tree_id) = self.store.last_tree().await { + let tree = self.store.tree(&last_tree_id).await; + let mut id = last_tree_id; + while Self::find_last_child(&tree, &mut id) {} + *cursor = Cursor::Msg(id); + } + } + Cursor::Msg(ref mut msg) => { + let path = self.store.path(msg).await; + let mut tree = self.store.tree(path.first()).await; + self.find_prev_msg(&mut tree, msg).await; + } + _ => {} } } - pub async fn move_down>( - &mut self, - store: &S, - cursor: &mut Option>, - frame: &mut Frame, - size: Size, - ) { - let old_blocks = self - .layout_blocks(store, cursor.as_ref(), frame, size) - .await; - let old_cursor_id = cursor.as_ref().map(|c| c.id.clone()); - - if let Some(cursor) = cursor { - let path = store.path(&cursor.id).await; - let mut tree = store.tree(path.first()).await; - self.find_next_msg(store, &mut tree, &mut cursor.id).await; - } - // If that condition doesn't hold, we're already at the bottom in - // cursor-less mode and can't move further down anyways. - - if let Some(cursor) = cursor { - self.correct_cursor_offset(store, frame, size, &old_blocks, &old_cursor_id, cursor) - .await; + pub async fn move_down(&mut self, cursor: &mut Cursor) { + if let Cursor::Msg(ref mut msg) = cursor { + let path = self.store.path(msg).await; + let mut tree = self.store.tree(path.first()).await; + if !self.find_next_msg(&mut tree, msg).await { + *cursor = Cursor::Bottom; + } } } + pub async fn move_to_first(&mut self, cursor: &mut Cursor) { + if let Some(tree_id) = self.store.first_tree().await { + *cursor = Cursor::Msg(tree_id); + } + } + + pub async fn move_to_last(&mut self, cursor: &mut Cursor) { + *cursor = Cursor::Bottom; + } +} + +/* pub async fn move_up_sibling>( &mut self, store: &S, @@ -310,53 +254,6 @@ impl TreeView { } } - pub async fn move_to_first>( - &mut self, - store: &S, - cursor: &mut Option>, - frame: &mut Frame, - size: Size, - ) { - let old_blocks = self - .layout_blocks(store, cursor.as_ref(), frame, size) - .await; - let old_cursor_id = cursor.as_ref().map(|c| c.id.clone()); - - if let Some(tree_id) = store.first_tree().await { - *cursor = Some(Cursor::new(tree_id)); - } - - if let Some(cursor) = cursor { - self.correct_cursor_offset(store, frame, size, &old_blocks, &old_cursor_id, cursor) - .await; - } - } - - pub async fn move_to_last>( - &mut self, - store: &S, - cursor: &mut Option>, - frame: &mut Frame, - size: Size, - ) { - let old_blocks = self - .layout_blocks(store, cursor.as_ref(), frame, size) - .await; - let old_cursor_id = cursor.as_ref().map(|c| c.id.clone()); - - if let Some(tree_id) = store.last_tree().await { - let tree = store.tree(&tree_id).await; - let mut id = tree_id; - while Self::find_last_child(&tree, &mut id) {} - *cursor = Some(Cursor::new(id)); - } - - if let Some(cursor) = cursor { - self.correct_cursor_offset(store, frame, size, &old_blocks, &old_cursor_id, cursor) - .await; - } - } - // TODO move_older[_unseen] // TODO move_newer[_unseen] @@ -378,4 +275,4 @@ impl TreeView { .await; } } -} +*/ diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index e1582db..6b3d128 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -5,7 +5,8 @@ use toss::frame::{Frame, Size}; use crate::store::{Msg, MsgStore, Path, Tree}; use super::blocks::{Block, BlockBody, Blocks, MsgBlock}; -use super::{util, Cursor, InnerTreeViewState}; +use super::cursor::Cursor; +use super::{util, InnerTreeViewState}; impl> InnerTreeViewState { async fn cursor_path(&self, cursor: &Cursor) -> Path {