diff --git a/cove-tui/src/chat.rs b/cove-tui/src/chat.rs index ce61cc7..4c6829e 100644 --- a/cove-tui/src/chat.rs +++ b/cove-tui/src/chat.rs @@ -1,10 +1,9 @@ mod tree; use crossterm::event::KeyEvent; -use crossterm::style::ContentStyle; use toss::frame::{Frame, Pos, Size}; -use crate::traits::{Msg, MsgStore}; +use crate::store::{Msg, MsgStore}; use self::tree::TreeView; @@ -14,19 +13,28 @@ 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, +} + pub struct Chat> { store: S, - cursor: Option, + room: String, + cursor: Option>, mode: Mode, - tree: TreeView, + tree: TreeView, // thread: ThreadView, // flat: FlatView, } impl> Chat { - pub fn new(store: S) -> Self { + pub fn new(store: S, room: String) -> Self { Self { store, + room, cursor: None, mode: Mode::Tree, tree: TreeView::new(), @@ -37,16 +45,21 @@ impl> Chat { impl> Chat { pub fn handle_key_event(&mut self, event: KeyEvent, size: Size) { match self.mode { - Mode::Tree => { - self.tree - .handle_key_event(&mut self.store, &mut self.cursor, event, size) - } + Mode::Tree => self.tree.handle_key_event( + &mut self.store, + &self.room, + &mut self.cursor, + event, + size, + ), } } pub fn render(&mut self, frame: &mut Frame, pos: Pos, size: Size) { match self.mode { - Mode::Tree => self.tree.render(&mut self.store, frame, pos, size), + Mode::Tree => self + .tree + .render(&mut self.store, &self.room, frame, pos, size), } } } diff --git a/cove-tui/src/chat/tree.rs b/cove-tui/src/chat/tree.rs index 61405b8..6dba075 100644 --- a/cove-tui/src/chat/tree.rs +++ b/cove-tui/src/chat/tree.rs @@ -1,32 +1,144 @@ +use std::collections::VecDeque; +use std::marker::PhantomData; + +use chrono::{DateTime, Utc}; use crossterm::event::KeyEvent; use crossterm::style::ContentStyle; use toss::frame::{Frame, Pos, Size}; -use crate::traits::{Msg, MsgStore}; +use crate::store::{Msg, MsgStore}; -pub struct TreeView {} +use super::Cursor; -impl TreeView { +struct Block { + line: i32, + height: i32, + id: Option, + cursor: bool, + content: BlockContent, +} + +enum BlockContent { + Msg(MsgBlock), + Placeholder, +} + +struct MsgBlock { + time: DateTime, + indent: usize, + nick: String, + content: Vec, +} + +struct Layout(VecDeque>); + +impl Layout { pub fn new() -> Self { - Self {} + Self(VecDeque::new()) } - pub fn handle_key_event( + fn mark_cursor(&mut self, id: &I) { + for block in &mut self.0 { + if block.id.as_ref() == Some(id) { + block.cursor = true; + } + } + } + + fn calculate_offsets_with_cursor(&mut self, line: i32) { + let cursor_index = self + .0 + .iter() + .enumerate() + .find(|(_, b)| b.cursor) + .expect("layout must contain cursor block") + .0; + + // Propagate lines from cursor to both ends + self.0[cursor_index].line = line; + for i in (0..cursor_index).rev() { + // let succ_line = self.0[i + 1].line; + // let curr = &mut self.0[i]; + // curr.line = succ_line - curr.height; + self.0[i].line = self.0[i + 1].line - self.0[i].height; + } + for i in (cursor_index + 1)..self.0.len() { + // let pred = &self.0[i - 1]; + // self.0[i].line = pred.line + pred.height; + self.0[i].line = self.0[i - 1].line + self.0[i - 1].height; + } + } + + fn calculate_offsets_without_cursor(&mut self, height: i32) { + if let Some(back) = self.0.back_mut() { + back.line = height - back.height; + } + for i in (0..self.0.len() - 1).rev() { + self.0[i].line = self.0[i + 1].line - self.0[i].height; + } + } + + pub fn calculate_offsets(&mut self, height: i32, cursor: Option>) { + if let Some(cursor) = cursor { + let line = ((height - 1) as f32 * cursor.proportion) as i32; + self.mark_cursor(&cursor.id); + self.calculate_offsets_with_cursor(line); + } else { + self.calculate_offsets_without_cursor(height); + } + } +} + +pub struct TreeView { + // pub focus: Option, + // pub folded: HashSet, + // pub minimized: HashSet, + phantom: PhantomData, // TODO Remove +} + +impl TreeView { + pub fn new() -> Self { + Self { + phantom: PhantomData, + } + } + + async fn layout>( + &mut self, + room: &str, + store: S, + cursor: &mut Option>, + ) -> Layout { + if let Some(cursor) = cursor { + // TODO Ensure focus lies on cursor path, otherwise unfocus + // TODO Unfold all messages on path to cursor + let cursor_path = store.path(room, &cursor.id).await; + // TODO Produce layout of cursor subtree (with correct offsets) + // TODO Expand layout upwards and downwards if there is no focus + todo!() + } else { + // TODO Ensure there is no focus + // TODO Produce layout of last tree (with correct offsets) + // TODO Expand layout upwards + todo!() + } + } + + pub fn handle_key_event>( &mut self, store: &mut S, - cursor: &mut Option, + room: &str, + cursor: &mut Option>, event: KeyEvent, size: Size, - ) where - M: Msg, - S: MsgStore, - { + ) { // TODO } - pub fn render>( + pub fn render>( &mut self, store: &mut S, + room: &str, frame: &mut Frame, pos: Pos, size: Size, diff --git a/cove-tui/src/store.rs b/cove-tui/src/store.rs index 39eab24..1ff56e1 100644 --- a/cove-tui/src/store.rs +++ b/cove-tui/src/store.rs @@ -84,6 +84,6 @@ impl Tree { #[async_trait] pub trait MsgStore { - async fn path(&self, room: &str, id: M::Id) -> Path; - async fn thread(&self, room: &str, root: M::Id) -> Tree; + async fn path(&self, room: &str, id: &M::Id) -> Path; + async fn thread(&self, room: &str, root: &M::Id) -> Tree; } diff --git a/cove-tui/src/store/dummy.rs b/cove-tui/src/store/dummy.rs index af8d61c..6b5d6b3 100644 --- a/cove-tui/src/store/dummy.rs +++ b/cove-tui/src/store/dummy.rs @@ -80,12 +80,12 @@ impl DummyStore { self } - fn tree(&self, id: usize, result: &mut Vec) { + fn collect_tree(&self, id: usize, result: &mut Vec) { if let Some(msg) = self.msgs.get(&id) { result.push(msg.clone()); if let Some(children) = self.children.get(&id) { for child in children { - self.tree(*child, result); + self.collect_tree(*child, result); } } } @@ -94,7 +94,8 @@ impl DummyStore { #[async_trait] impl MsgStore for DummyStore { - async fn path(&self, _room: &str, mut id: usize) -> Path { + async fn path(&self, _room: &str, id: &usize) -> Path { + let mut id = *id; let mut segments = vec![id]; while let Some(parent) = self.msgs.get(&id).and_then(|msg| msg.parent) { segments.push(parent); @@ -104,9 +105,9 @@ impl MsgStore for DummyStore { Path::new(segments) } - async fn thread(&self, _room: &str, root: usize) -> Tree { + async fn thread(&self, _room: &str, root: &usize) -> Tree { let mut msgs = vec![]; - - Tree::new(root, msgs) + self.collect_tree(*root, &mut msgs); + Tree::new(*root, msgs) } } diff --git a/cove-tui/src/ui.rs b/cove-tui/src/ui.rs index a8f7b44..ce52bfa 100644 --- a/cove-tui/src/ui.rs +++ b/cove-tui/src/ui.rs @@ -46,7 +46,7 @@ impl Ui { let store = DummyStore::new() .msg(DummyMsg::new(1, "nick", "content")) .msg(DummyMsg::new(2, "Some1Else", "reply").parent(1)); - let chat = Chat::new(store); + let chat = Chat::new(store, "testroom".to_string()); // Run main UI. //