Start implementing proper cursor movement
This commit is contained in:
parent
bec12917d6
commit
ea6b345fa9
5 changed files with 148 additions and 46 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
mod blocks;
|
mod blocks;
|
||||||
|
mod cursor;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod render;
|
mod render;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
@ -26,48 +27,6 @@ impl<M: Msg> TreeView<M> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn move_to_prev_msg<S: MsgStore<M>>(
|
|
||||||
&mut self,
|
|
||||||
store: &mut S,
|
|
||||||
room: &str,
|
|
||||||
cursor: &mut Option<Cursor<M::Id>>,
|
|
||||||
) {
|
|
||||||
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<Cursor<M::Id>>) {
|
|
||||||
if let Some(cursor) = cursor {
|
|
||||||
cursor.proportion = 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_key_event<S: MsgStore<M>>(
|
pub async fn handle_key_event<S: MsgStore<M>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
store: &mut S,
|
store: &mut S,
|
||||||
|
|
@ -93,7 +52,9 @@ impl<M: Msg> TreeView<M> {
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
size: Size,
|
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);
|
Self::render_blocks(frame, pos, size, &blocks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,12 +112,12 @@ impl<I: PartialEq> Blocks<I> {
|
||||||
if &block.id == id {
|
if &block.id == id {
|
||||||
block.cursor = true;
|
block.cursor = true;
|
||||||
if cursor.is_some() {
|
if cursor.is_some() {
|
||||||
panic!("more than one cursor in layout");
|
panic!("more than one cursor in blocks");
|
||||||
}
|
}
|
||||||
cursor = Some(i);
|
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<I>, height: u16) {
|
pub fn calculate_offsets_with_cursor(&mut self, cursor: &Cursor<I>, height: u16) {
|
||||||
|
|
@ -165,4 +165,8 @@ impl<I: PartialEq> Blocks<I> {
|
||||||
self.push_back(block);
|
self.push_back(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find(&self, id: &I) -> Option<&Block<I>> {
|
||||||
|
self.blocks.iter().find(|b| &b.id == id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
134
cove-tui/src/chat/tree/cursor.rs
Normal file
134
cove-tui/src/chat/tree/cursor.rs
Normal file
|
|
@ -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<M: Msg> TreeView<M> {
|
||||||
|
async fn correct_cursor_offset<S: MsgStore<M>>(
|
||||||
|
&mut self,
|
||||||
|
room: &str,
|
||||||
|
store: &S,
|
||||||
|
frame: &mut Frame,
|
||||||
|
size: Size,
|
||||||
|
old_blocks: &Blocks<M::Id>,
|
||||||
|
old_cursor_id: &M::Id,
|
||||||
|
cursor: &mut Cursor<M::Id>,
|
||||||
|
) {
|
||||||
|
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<S: MsgStore<M>>(
|
||||||
|
&mut self,
|
||||||
|
store: &mut S,
|
||||||
|
room: &str,
|
||||||
|
cursor: &mut Option<Cursor<M::Id>>,
|
||||||
|
) {
|
||||||
|
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<Cursor<M::Id>>) {
|
||||||
|
if let Some(cursor) = cursor {
|
||||||
|
cursor.proportion = 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -91,11 +91,12 @@ impl<M: Msg> TreeView<M> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Split up based on cursor presence
|
||||||
pub async fn layout_blocks<S: MsgStore<M>>(
|
pub async fn layout_blocks<S: MsgStore<M>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
room: &str,
|
room: &str,
|
||||||
store: &S,
|
store: &S,
|
||||||
cursor: &Option<Cursor<M::Id>>,
|
cursor: Option<&Cursor<M::Id>>,
|
||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
size: Size,
|
size: Size,
|
||||||
) -> Blocks<M::Id> {
|
) -> Blocks<M::Id> {
|
||||||
|
|
@ -111,6 +112,7 @@ impl<M: Msg> TreeView<M> {
|
||||||
layout.calculate_offsets_with_cursor(cursor, size.height);
|
layout.calculate_offsets_with_cursor(cursor, size.height);
|
||||||
|
|
||||||
// Expand upwards and downwards
|
// Expand upwards and downwards
|
||||||
|
// TODO Ensure that blocks are scrolled correctly
|
||||||
// TODO Don't do this if there is a focus
|
// TODO Don't do this if there is a focus
|
||||||
if let Some(prev_tree) = store.prev_tree(room, cursor_tree_id).await {
|
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;
|
Self::expand_blocks_up(room, store, frame, size, &mut layout, prev_tree).await;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ pub trait Msg {
|
||||||
fn content(&self) -> String;
|
fn content(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Path<I>(Vec<I>);
|
pub struct Path<I>(Vec<I>);
|
||||||
|
|
||||||
impl<I> Path<I> {
|
impl<I> Path<I> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue