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 {
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(),
}
}

View file

@ -61,9 +61,9 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
&self.store
}
pub fn widget(&self) -> Chat<M, S> {
pub fn widget(&self, nick: String) -> Chat<M, S> {
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))))
}
pub fn widget(&self) -> TreeView<M, S> {
TreeView(self.0.clone())
pub fn widget(&self, nick: String) -> TreeView<M, S> {
TreeView {
inner: self.0.clone(),
nick,
}
}
pub async fn handle_navigation(&mut self, event: KeyEvent) -> bool {
@ -114,7 +117,10 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
// 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]
impl<M, S> Widget for TreeView<M, S>
@ -128,8 +134,8 @@ where
}
async fn render(self: Box<Self>, 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 {

View file

@ -51,8 +51,20 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
.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(
&self,
nick: &str,
frame: &mut Frame,
tree: &Tree<M>,
indent: usize,
@ -78,7 +90,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
// 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<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
// 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<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 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<M::Id> {
fn layout_bottom(&self, nick: &str, frame: &mut Frame) -> TreeBlocks<M::Id> {
let mut blocks = TreeBlocks::new(Root::Bottom, Root::Bottom);
// 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);
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<M::Id>) {
async fn expand_to_top(&self, nick: &str, frame: &mut Frame, blocks: &mut TreeBlocks<M::Id>) {
let top_line = 0;
while blocks.blocks().top_line > top_line {
@ -144,11 +166,16 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
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<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;
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 {
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<M::Id>,
) {
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<M::Id>,
) -> TreeBlocks<M::Id> {
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<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
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<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} => {
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<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
async fn layout_cursor_seed(
&self,
nick: &str,
frame: &mut Frame,
last_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::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<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} => {
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<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
async fn layout_initial_seed(
&self,
nick: &str,
frame: &mut Frame,
last_cursor_path: &Path<M::Id>,
cursor_path: &Path<M::Id>,
) -> TreeBlocks<M::Id> {
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<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:
//
// 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 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<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
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;
}
}

View file

@ -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<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(
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(