diff --git a/src/ui.rs b/src/ui.rs index 36e4413..b29448e 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -184,7 +184,7 @@ impl Ui { async fn widget(&mut self) -> BoxedWidget { match self.mode { Mode::Main => self.rooms.widget().await, - Mode::Log => self.log_chat.widget().into(), + Mode::Log => self.log_chat.widget(String::new()).into(), } } diff --git a/src/ui/chat.rs b/src/ui/chat.rs index 9a5ecb3..1ec0fea 100644 --- a/src/ui/chat.rs +++ b/src/ui/chat.rs @@ -61,9 +61,9 @@ impl> ChatState { &self.store } - pub fn widget(&self) -> Chat { + pub fn widget(&self, nick: String) -> Chat { match self.mode { - Mode::Tree => Chat::Tree(self.tree.widget()), + Mode::Tree => Chat::Tree(self.tree.widget(nick)), } } diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index 7b5a440..c0cddc4 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -88,8 +88,11 @@ impl> TreeViewState { Self(Arc::new(Mutex::new(InnerTreeViewState::new(store)))) } - pub fn widget(&self) -> TreeView { - TreeView(self.0.clone()) + pub fn widget(&self, nick: String) -> TreeView { + TreeView { + inner: self.0.clone(), + nick, + } } pub async fn handle_navigation(&mut self, event: KeyEvent) -> bool { @@ -114,7 +117,10 @@ impl> TreeViewState { // Widget // //////////// -pub struct TreeView>(Arc>>); +pub struct TreeView> { + inner: Arc>>, + nick: String, +} #[async_trait] impl Widget for TreeView @@ -128,8 +134,8 @@ where } async fn render(self: Box, frame: &mut Frame) { - let mut guard = self.0.lock().await; - let blocks = guard.relayout(frame).await; + let mut guard = self.inner.lock().await; + let blocks = guard.relayout(&self.nick, frame).await; let size = frame.size(); for block in blocks.into_blocks().blocks { diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index dd2dacd..7a324dd 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -51,8 +51,20 @@ impl> InnerTreeViewState { .is_some() } + fn editor_block(&self, nick: &str, frame: &mut Frame, indent: usize) -> Block> { + let (widget, cursor_line) = widgets::editor::(frame, indent, nick, &self.editor); + let cursor_line = cursor_line as i32; + Block::new(frame, BlockId::Cursor, widget).focus(cursor_line..cursor_line + 1) + } + + fn pseudo_block(&self, nick: &str, frame: &mut Frame, indent: usize) -> Block> { + let widget = widgets::pseudo::(indent, nick, &self.editor); + Block::new(frame, BlockId::Cursor, widget) + } + fn layout_subtree( &self, + nick: &str, frame: &mut Frame, tree: &Tree, indent: usize, @@ -78,7 +90,7 @@ impl> InnerTreeViewState { // Children, recursively if let Some(children) = tree.children(id) { for child in children { - self.layout_subtree(frame, tree, indent + 1, child, blocks); + self.layout_subtree(nick, frame, tree, indent + 1, child, blocks); } } @@ -90,20 +102,29 @@ impl> InnerTreeViewState { // Trailing editor or pseudomessage if self.cursor.refers_to_last_child_of(id) { + match self.cursor { + Cursor::Editor { .. } => blocks + .blocks_mut() + .push_back(self.editor_block(nick, frame, indent)), + Cursor::Pseudo { .. } => blocks + .blocks_mut() + .push_back(self.pseudo_block(nick, frame, indent)), + _ => {} + } // TODO Render proper editor or pseudocursor let block = Block::new(frame, BlockId::Cursor, Text::new("TODO")); blocks.blocks_mut().push_back(block); } } - fn layout_tree(&self, frame: &mut Frame, tree: Tree) -> TreeBlocks { + fn layout_tree(&self, nick: &str, frame: &mut Frame, tree: Tree) -> TreeBlocks { let root = Root::Tree(tree.root().clone()); let mut blocks = TreeBlocks::new(root.clone(), root); - self.layout_subtree(frame, &tree, 0, tree.root(), &mut blocks); + self.layout_subtree(nick, frame, &tree, 0, tree.root(), &mut blocks); blocks } - fn layout_bottom(&self, frame: &mut Frame) -> TreeBlocks { + fn layout_bottom(&self, nick: &str, frame: &mut Frame) -> TreeBlocks { let mut blocks = TreeBlocks::new(Root::Bottom, Root::Bottom); // Ghost cursor, for positioning according to last cursor line @@ -119,18 +140,19 @@ impl> InnerTreeViewState { let block = Block::new(frame, BlockId::Cursor, Empty); blocks.blocks_mut().push_back(block); } - Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { - // TODO Render proper editor or pseudocursor - let block = Block::new(frame, BlockId::Cursor, Text::new("TODO")); - blocks.blocks_mut().push_back(block); - } + Cursor::Editor { parent: None, .. } => blocks + .blocks_mut() + .push_back(self.editor_block(nick, frame, 0)), + Cursor::Pseudo { parent: None, .. } => blocks + .blocks_mut() + .push_back(self.pseudo_block(nick, frame, 0)), _ => {} } blocks } - async fn expand_to_top(&self, frame: &mut Frame, blocks: &mut TreeBlocks) { + async fn expand_to_top(&self, nick: &str, frame: &mut Frame, blocks: &mut TreeBlocks) { let top_line = 0; while blocks.blocks().top_line > top_line { @@ -144,11 +166,16 @@ impl> InnerTreeViewState { None => break, }; let prev_tree = self.store.tree(&prev_tree_id).await; - blocks.prepend(self.layout_tree(frame, prev_tree)); + blocks.prepend(self.layout_tree(nick, frame, prev_tree)); } } - async fn expand_to_bottom(&self, frame: &mut Frame, blocks: &mut TreeBlocks) { + async fn expand_to_bottom( + &self, + nick: &str, + frame: &mut Frame, + blocks: &mut TreeBlocks, + ) { let bottom_line = frame.size().height as i32 - 1; while blocks.blocks().bottom_line < bottom_line { @@ -159,44 +186,46 @@ impl> InnerTreeViewState { }; if let Some(next_tree_id) = next_tree_id { let next_tree = self.store.tree(&next_tree_id).await; - blocks.append(self.layout_tree(frame, next_tree)); + blocks.append(self.layout_tree(nick, frame, next_tree)); } else { - blocks.append(self.layout_bottom(frame)); + blocks.append(self.layout_bottom(nick, frame)); } } } async fn fill_screen_and_clamp_scrolling( &self, + nick: &str, frame: &mut Frame, blocks: &mut TreeBlocks, ) { let top_line = 0; let bottom_line = frame.size().height as i32 - 1; - self.expand_to_top(frame, blocks).await; + self.expand_to_top(nick, frame, blocks).await; if blocks.blocks().top_line > top_line { blocks.blocks_mut().set_top_line(0); } - self.expand_to_bottom(frame, blocks).await; + self.expand_to_bottom(nick, frame, blocks).await; if blocks.blocks().bottom_line < bottom_line { blocks.blocks_mut().set_bottom_line(bottom_line); } - self.expand_to_top(frame, blocks).await; + self.expand_to_top(nick, frame, blocks).await; } async fn layout_last_cursor_seed( &self, + nick: &str, frame: &mut Frame, last_cursor_path: &Path, ) -> TreeBlocks { match &self.last_cursor { Cursor::Bottom => { - let mut blocks = self.layout_bottom(frame); + let mut blocks = self.layout_bottom(nick, frame); let bottom_line = frame.size().height as i32 - 1; blocks.blocks_mut().set_bottom_line(bottom_line); @@ -204,7 +233,7 @@ impl> InnerTreeViewState { blocks } Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { - let mut blocks = self.layout_bottom(frame); + let mut blocks = self.layout_bottom(nick, frame); blocks .blocks_mut() @@ -221,7 +250,7 @@ impl> InnerTreeViewState { } => { let root = last_cursor_path.first(); let tree = self.store.tree(root).await; - let mut blocks = self.layout_tree(frame, tree); + let mut blocks = self.layout_tree(nick, frame, tree); blocks .blocks_mut() @@ -234,6 +263,7 @@ impl> InnerTreeViewState { async fn layout_cursor_seed( &self, + nick: &str, frame: &mut Frame, last_cursor_path: &Path, cursor_path: &Path, @@ -244,7 +274,7 @@ impl> InnerTreeViewState { Cursor::Bottom | Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { - let mut blocks = self.layout_bottom(frame); + let mut blocks = self.layout_bottom(nick, frame); blocks.blocks_mut().set_bottom_line(bottom_line); @@ -259,7 +289,7 @@ impl> InnerTreeViewState { } => { let root = cursor_path.first(); let tree = self.store.tree(root).await; - let mut blocks = self.layout_tree(frame, tree); + let mut blocks = self.layout_tree(nick, frame, tree); let cursor_above_last = cursor_path < last_cursor_path; let cursor_line = if cursor_above_last { 0 } else { bottom_line }; @@ -274,15 +304,17 @@ impl> InnerTreeViewState { async fn layout_initial_seed( &self, + nick: &str, frame: &mut Frame, last_cursor_path: &Path, cursor_path: &Path, ) -> TreeBlocks { if let Cursor::Bottom = self.cursor { - self.layout_cursor_seed(frame, last_cursor_path, cursor_path) + self.layout_cursor_seed(nick, frame, last_cursor_path, cursor_path) .await } else { - self.layout_last_cursor_seed(frame, last_cursor_path).await + self.layout_last_cursor_seed(nick, frame, last_cursor_path) + .await } } @@ -377,7 +409,7 @@ impl> InnerTreeViewState { } } - pub async fn relayout(&mut self, frame: &mut Frame) -> TreeBlocks { + pub async fn relayout(&mut self, nick: &str, frame: &mut Frame) -> TreeBlocks { // The basic idea is this: // // First, layout a full screen of blocks around self.last_cursor, using @@ -400,24 +432,24 @@ impl> InnerTreeViewState { let cursor_path = self.cursor_path(&self.cursor).await; let mut blocks = self - .layout_initial_seed(frame, &last_cursor_path, &cursor_path) + .layout_initial_seed(nick, frame, &last_cursor_path, &cursor_path) .await; blocks.blocks_mut().offset(self.scroll); - self.fill_screen_and_clamp_scrolling(frame, &mut blocks) + self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks) .await; if !self.contains_cursor(&blocks) { blocks = self - .layout_cursor_seed(frame, &last_cursor_path, &cursor_path) + .layout_cursor_seed(nick, frame, &last_cursor_path, &cursor_path) .await; - self.fill_screen_and_clamp_scrolling(frame, &mut blocks) + self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks) .await; } match self.correction { Some(Correction::MakeCursorVisible) => { self.scroll_so_cursor_is_visible(frame, &mut blocks); - self.fill_screen_and_clamp_scrolling(frame, &mut blocks) + self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks) .await; } Some(Correction::MoveCursorToVisibleArea) => { @@ -433,8 +465,10 @@ impl> InnerTreeViewState { self.correction = None; 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) + blocks = self + .layout_last_cursor_seed(nick, frame, &last_cursor_path) + .await; + self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks) .await; } } diff --git a/src/ui/chat/tree/widgets.rs b/src/ui/chat/tree/widgets.rs index 25f2e9e..62408cd 100644 --- a/src/ui/chat/tree/widgets.rs +++ b/src/ui/chat/tree/widgets.rs @@ -4,9 +4,11 @@ mod indent; mod time; use crossterm::style::{ContentStyle, Stylize}; +use toss::frame::Frame; use super::super::ChatMsg; use crate::store::Msg; +use crate::ui::widgets::editor::EditorState; use crate::ui::widgets::join::{HJoin, Segment}; use crate::ui::widgets::layer::Layer; use crate::ui::widgets::padding::Padding; @@ -53,3 +55,50 @@ pub fn msg_placeholder(highlighted: bool, indent: usize) -> BoxedWidget { ]) .into() } + +pub fn editor( + frame: &mut Frame, + indent: usize, + nick: &str, + editor: &EditorState, +) -> (BoxedWidget, usize) { + let (nick, content) = M::edit(nick, &editor.text()); + let editor = editor.widget().highlight(|_| content); + let cursor_row = editor.cursor_row(frame); + + let widget = HJoin::new(vec![ + Segment::new( + Padding::new(time::widget(None, true)) + .stretch(true) + .right(1), + ), + Segment::new(Indent::new(indent, true)), + Segment::new(Layer::new(vec![ + Indent::new(1, false).into(), + Padding::new(Text::new(nick)).right(1).into(), + ])), + Segment::new(editor).priority(1), + ]) + .into(); + + (widget, cursor_row) +} + +pub fn pseudo(indent: usize, nick: &str, editor: &EditorState) -> BoxedWidget { + let (nick, content) = M::edit(nick, &editor.text()); + + HJoin::new(vec![ + Segment::new( + Padding::new(time::widget(None, true)) + .stretch(true) + .right(1), + ), + Segment::new(Indent::new(indent, true)), + Segment::new(Layer::new(vec![ + Indent::new(1, false).into(), + Padding::new(Text::new(nick)).right(1).into(), + ])), + Segment::new(Text::new(content).wrap(true)).priority(1), + ]) + .into() +} diff --git a/src/ui/room.rs b/src/ui/room.rs index be67d01..b9b1f18 100644 --- a/src/ui/room.rs +++ b/src/ui/room.rs @@ -118,7 +118,8 @@ impl EuphRoom { Segment::new(Border::new( Padding::new(self.status_widget(status)).horizontal(1), )), - Segment::new(self.chat.widget()).expanding(true), + // TODO Use last known nick? + Segment::new(self.chat.widget(String::new())).expanding(true), ]) .into() } @@ -133,7 +134,7 @@ impl EuphRoom { Segment::new(Border::new( Padding::new(self.status_widget(status)).horizontal(1), )), - Segment::new(self.chat.widget()).expanding(true), + Segment::new(self.chat.widget(joined.session.name.clone())).expanding(true), ])) .expanding(true), Segment::new(Border::new(