Render editor and pseudo message

This commit is contained in:
Joscha 2022-08-01 23:37:41 +02:00
parent 4ead592e59
commit 415da3afd8
6 changed files with 132 additions and 42 deletions

View file

@ -184,7 +184,7 @@ impl Ui {
async fn widget(&mut self) -> BoxedWidget { async fn widget(&mut self) -> BoxedWidget {
match self.mode { match self.mode {
Mode::Main => self.rooms.widget().await, Mode::Main => self.rooms.widget().await,
Mode::Log => self.log_chat.widget().into(), Mode::Log => self.log_chat.widget(String::new()).into(),
} }
} }

View file

@ -61,9 +61,9 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
&self.store &self.store
} }
pub fn widget(&self) -> Chat<M, S> { pub fn widget(&self, nick: String) -> Chat<M, S> {
match self.mode { match self.mode {
Mode::Tree => Chat::Tree(self.tree.widget()), Mode::Tree => Chat::Tree(self.tree.widget(nick)),
} }
} }

View file

@ -88,8 +88,11 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
Self(Arc::new(Mutex::new(InnerTreeViewState::new(store)))) Self(Arc::new(Mutex::new(InnerTreeViewState::new(store))))
} }
pub fn widget(&self) -> TreeView<M, S> { pub fn widget(&self, nick: String) -> TreeView<M, S> {
TreeView(self.0.clone()) TreeView {
inner: self.0.clone(),
nick,
}
} }
pub async fn handle_navigation(&mut self, event: KeyEvent) -> bool { pub async fn handle_navigation(&mut self, event: KeyEvent) -> bool {
@ -114,7 +117,10 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
// Widget // // Widget //
//////////// ////////////
pub struct TreeView<M: Msg, S: MsgStore<M>>(Arc<Mutex<InnerTreeViewState<M, S>>>); pub struct TreeView<M: Msg, S: MsgStore<M>> {
inner: Arc<Mutex<InnerTreeViewState<M, S>>>,
nick: String,
}
#[async_trait] #[async_trait]
impl<M, S> Widget for TreeView<M, S> impl<M, S> Widget for TreeView<M, S>
@ -128,8 +134,8 @@ where
} }
async fn render(self: Box<Self>, frame: &mut Frame) { async fn render(self: Box<Self>, frame: &mut Frame) {
let mut guard = self.0.lock().await; let mut guard = self.inner.lock().await;
let blocks = guard.relayout(frame).await; let blocks = guard.relayout(&self.nick, frame).await;
let size = frame.size(); let size = frame.size();
for block in blocks.into_blocks().blocks { for block in blocks.into_blocks().blocks {

View file

@ -51,8 +51,20 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
.is_some() .is_some()
} }
fn editor_block(&self, nick: &str, frame: &mut Frame, indent: usize) -> Block<BlockId<M::Id>> {
let (widget, cursor_line) = widgets::editor::<M>(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<BlockId<M::Id>> {
let widget = widgets::pseudo::<M>(indent, nick, &self.editor);
Block::new(frame, BlockId::Cursor, widget)
}
fn layout_subtree( fn layout_subtree(
&self, &self,
nick: &str,
frame: &mut Frame, frame: &mut Frame,
tree: &Tree<M>, tree: &Tree<M>,
indent: usize, indent: usize,
@ -78,7 +90,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
// Children, recursively // Children, recursively
if let Some(children) = tree.children(id) { if let Some(children) = tree.children(id) {
for child in children { 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<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
// Trailing editor or pseudomessage // Trailing editor or pseudomessage
if self.cursor.refers_to_last_child_of(id) { 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 // TODO Render proper editor or pseudocursor
let block = Block::new(frame, BlockId::Cursor, Text::new("TODO")); let block = Block::new(frame, BlockId::Cursor, Text::new("TODO"));
blocks.blocks_mut().push_back(block); blocks.blocks_mut().push_back(block);
} }
} }
fn layout_tree(&self, frame: &mut Frame, tree: Tree<M>) -> TreeBlocks<M::Id> { fn layout_tree(&self, nick: &str, frame: &mut Frame, tree: Tree<M>) -> TreeBlocks<M::Id> {
let root = Root::Tree(tree.root().clone()); let root = Root::Tree(tree.root().clone());
let mut blocks = TreeBlocks::new(root.clone(), root); 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 blocks
} }
fn layout_bottom(&self, frame: &mut Frame) -> TreeBlocks<M::Id> { fn layout_bottom(&self, nick: &str, frame: &mut Frame) -> TreeBlocks<M::Id> {
let mut blocks = TreeBlocks::new(Root::Bottom, Root::Bottom); let mut blocks = TreeBlocks::new(Root::Bottom, Root::Bottom);
// Ghost cursor, for positioning according to last cursor line // Ghost cursor, for positioning according to last cursor line
@ -119,18 +140,19 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
let block = Block::new(frame, BlockId::Cursor, Empty); let block = Block::new(frame, BlockId::Cursor, Empty);
blocks.blocks_mut().push_back(block); blocks.blocks_mut().push_back(block);
} }
Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { Cursor::Editor { parent: None, .. } => blocks
// TODO Render proper editor or pseudocursor .blocks_mut()
let block = Block::new(frame, BlockId::Cursor, Text::new("TODO")); .push_back(self.editor_block(nick, frame, 0)),
blocks.blocks_mut().push_back(block); Cursor::Pseudo { parent: None, .. } => blocks
} .blocks_mut()
.push_back(self.pseudo_block(nick, frame, 0)),
_ => {} _ => {}
} }
blocks blocks
} }
async fn expand_to_top(&self, frame: &mut Frame, blocks: &mut TreeBlocks<M::Id>) { async fn expand_to_top(&self, nick: &str, frame: &mut Frame, blocks: &mut TreeBlocks<M::Id>) {
let top_line = 0; let top_line = 0;
while blocks.blocks().top_line > top_line { while blocks.blocks().top_line > top_line {
@ -144,11 +166,16 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
None => break, None => break,
}; };
let prev_tree = self.store.tree(&prev_tree_id).await; 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<M::Id>) { async fn expand_to_bottom(
&self,
nick: &str,
frame: &mut Frame,
blocks: &mut TreeBlocks<M::Id>,
) {
let bottom_line = frame.size().height as i32 - 1; let bottom_line = frame.size().height as i32 - 1;
while blocks.blocks().bottom_line < bottom_line { while blocks.blocks().bottom_line < bottom_line {
@ -159,44 +186,46 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
}; };
if let Some(next_tree_id) = next_tree_id { if let Some(next_tree_id) = next_tree_id {
let next_tree = self.store.tree(&next_tree_id).await; 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 { } else {
blocks.append(self.layout_bottom(frame)); blocks.append(self.layout_bottom(nick, frame));
} }
} }
} }
async fn fill_screen_and_clamp_scrolling( async fn fill_screen_and_clamp_scrolling(
&self, &self,
nick: &str,
frame: &mut Frame, frame: &mut Frame,
blocks: &mut TreeBlocks<M::Id>, blocks: &mut TreeBlocks<M::Id>,
) { ) {
let top_line = 0; let top_line = 0;
let bottom_line = frame.size().height as i32 - 1; 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 { if blocks.blocks().top_line > top_line {
blocks.blocks_mut().set_top_line(0); 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 { if blocks.blocks().bottom_line < bottom_line {
blocks.blocks_mut().set_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( async fn layout_last_cursor_seed(
&self, &self,
nick: &str,
frame: &mut Frame, frame: &mut Frame,
last_cursor_path: &Path<M::Id>, last_cursor_path: &Path<M::Id>,
) -> TreeBlocks<M::Id> { ) -> TreeBlocks<M::Id> {
match &self.last_cursor { match &self.last_cursor {
Cursor::Bottom => { 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; let bottom_line = frame.size().height as i32 - 1;
blocks.blocks_mut().set_bottom_line(bottom_line); blocks.blocks_mut().set_bottom_line(bottom_line);
@ -204,7 +233,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
blocks blocks
} }
Cursor::Editor { parent: None, .. } | Cursor::Pseudo { parent: None, .. } => { 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
.blocks_mut() .blocks_mut()
@ -221,7 +250,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} => { } => {
let root = last_cursor_path.first(); let root = last_cursor_path.first();
let tree = self.store.tree(root).await; 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
.blocks_mut() .blocks_mut()
@ -234,6 +263,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
async fn layout_cursor_seed( async fn layout_cursor_seed(
&self, &self,
nick: &str,
frame: &mut Frame, frame: &mut Frame,
last_cursor_path: &Path<M::Id>, last_cursor_path: &Path<M::Id>,
cursor_path: &Path<M::Id>, cursor_path: &Path<M::Id>,
@ -244,7 +274,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
Cursor::Bottom Cursor::Bottom
| Cursor::Editor { parent: None, .. } | Cursor::Editor { parent: None, .. }
| Cursor::Pseudo { 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); blocks.blocks_mut().set_bottom_line(bottom_line);
@ -259,7 +289,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} => { } => {
let root = cursor_path.first(); let root = cursor_path.first();
let tree = self.store.tree(root).await; 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_above_last = cursor_path < last_cursor_path;
let cursor_line = if cursor_above_last { 0 } else { bottom_line }; let cursor_line = if cursor_above_last { 0 } else { bottom_line };
@ -274,15 +304,17 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
async fn layout_initial_seed( async fn layout_initial_seed(
&self, &self,
nick: &str,
frame: &mut Frame, frame: &mut Frame,
last_cursor_path: &Path<M::Id>, last_cursor_path: &Path<M::Id>,
cursor_path: &Path<M::Id>, cursor_path: &Path<M::Id>,
) -> TreeBlocks<M::Id> { ) -> TreeBlocks<M::Id> {
if let Cursor::Bottom = self.cursor { 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 .await
} else { } 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<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} }
} }
pub async fn relayout(&mut self, frame: &mut Frame) -> TreeBlocks<M::Id> { pub async fn relayout(&mut self, nick: &str, frame: &mut Frame) -> TreeBlocks<M::Id> {
// The basic idea is this: // The basic idea is this:
// //
// First, layout a full screen of blocks around self.last_cursor, using // First, layout a full screen of blocks around self.last_cursor, using
@ -400,24 +432,24 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
let cursor_path = self.cursor_path(&self.cursor).await; let cursor_path = self.cursor_path(&self.cursor).await;
let mut blocks = self let mut blocks = self
.layout_initial_seed(frame, &last_cursor_path, &cursor_path) .layout_initial_seed(nick, frame, &last_cursor_path, &cursor_path)
.await; .await;
blocks.blocks_mut().offset(self.scroll); 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; .await;
if !self.contains_cursor(&blocks) { if !self.contains_cursor(&blocks) {
blocks = self blocks = self
.layout_cursor_seed(frame, &last_cursor_path, &cursor_path) .layout_cursor_seed(nick, frame, &last_cursor_path, &cursor_path)
.await; .await;
self.fill_screen_and_clamp_scrolling(frame, &mut blocks) self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks)
.await; .await;
} }
match self.correction { match self.correction {
Some(Correction::MakeCursorVisible) => { Some(Correction::MakeCursorVisible) => {
self.scroll_so_cursor_is_visible(frame, &mut blocks); 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; .await;
} }
Some(Correction::MoveCursorToVisibleArea) => { Some(Correction::MoveCursorToVisibleArea) => {
@ -433,8 +465,10 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
self.correction = None; self.correction = None;
let last_cursor_path = self.store.path(&cursor_msg_id).await; let last_cursor_path = self.store.path(&cursor_msg_id).await;
blocks = self.layout_last_cursor_seed(frame, &last_cursor_path).await; blocks = self
self.fill_screen_and_clamp_scrolling(frame, &mut blocks) .layout_last_cursor_seed(nick, frame, &last_cursor_path)
.await;
self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks)
.await; .await;
} }
} }

View file

@ -4,9 +4,11 @@ mod indent;
mod time; mod time;
use crossterm::style::{ContentStyle, Stylize}; use crossterm::style::{ContentStyle, Stylize};
use toss::frame::Frame;
use super::super::ChatMsg; use super::super::ChatMsg;
use crate::store::Msg; use crate::store::Msg;
use crate::ui::widgets::editor::EditorState;
use crate::ui::widgets::join::{HJoin, Segment}; use crate::ui::widgets::join::{HJoin, Segment};
use crate::ui::widgets::layer::Layer; use crate::ui::widgets::layer::Layer;
use crate::ui::widgets::padding::Padding; use crate::ui::widgets::padding::Padding;
@ -53,3 +55,50 @@ pub fn msg_placeholder(highlighted: bool, indent: usize) -> BoxedWidget {
]) ])
.into() .into()
} }
pub fn editor<M: ChatMsg>(
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<M: ChatMsg>(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()
}

View file

@ -118,7 +118,8 @@ impl EuphRoom {
Segment::new(Border::new( Segment::new(Border::new(
Padding::new(self.status_widget(status)).horizontal(1), 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() .into()
} }
@ -133,7 +134,7 @@ impl EuphRoom {
Segment::new(Border::new( Segment::new(Border::new(
Padding::new(self.status_widget(status)).horizontal(1), 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), .expanding(true),
Segment::new(Border::new( Segment::new(Border::new(