Display chat cursor only when chat has focus

This commit is contained in:
Joscha 2022-09-25 21:53:36 +02:00
parent 9c9d9a51bb
commit 75e3a08b58
4 changed files with 81 additions and 48 deletions

View file

@ -66,10 +66,9 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
&self.store &self.store
} }
pub fn widget(&self, nick: String, focus: bool) -> Chat<M, S> { pub fn widget(&self, nick: String, focused: bool) -> Chat<M, S> {
// TODO Handle focus
match self.mode { match self.mode {
Mode::Tree => Chat::Tree(self.tree.widget(nick)), Mode::Tree => Chat::Tree(self.tree.widget(nick, focused)),
} }
} }
} }

View file

@ -370,10 +370,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, nick: String) -> TreeView<M, S> { pub fn widget(&self, nick: String, focused: bool) -> TreeView<M, S> {
TreeView { TreeView {
inner: self.0.clone(), inner: self.0.clone(),
nick, nick,
focused,
} }
} }
@ -411,6 +412,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
pub struct TreeView<M: Msg, S: MsgStore<M>> { pub struct TreeView<M: Msg, S: MsgStore<M>> {
inner: Arc<Mutex<InnerTreeViewState<M, S>>>, inner: Arc<Mutex<InnerTreeViewState<M, S>>>,
nick: String, nick: String,
focused: bool,
} }
#[async_trait] #[async_trait]
@ -426,7 +428,7 @@ where
async fn render(self: Box<Self>, frame: &mut Frame) { async fn render(self: Box<Self>, frame: &mut Frame) {
let mut guard = self.inner.lock().await; let mut guard = self.inner.lock().await;
let blocks = guard.relayout(&self.nick, frame).await; let blocks = guard.relayout(self.nick, self.focused, 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

@ -16,6 +16,11 @@ fn scrolloff(height: i32) -> i32 {
scrolloff.min(SCROLLOFF) scrolloff.min(SCROLLOFF)
} }
struct Context {
nick: String,
focused: bool,
}
impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> { impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
async fn cursor_path(&self, cursor: &Cursor<M::Id>) -> Path<M::Id> { async fn cursor_path(&self, cursor: &Cursor<M::Id>) -> Path<M::Id> {
match cursor { match cursor {
@ -64,20 +69,30 @@ 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>> { fn editor_block(
let (widget, cursor_row) = widgets::editor::<M>(frame, indent, nick, &self.editor); &self,
context: &Context,
frame: &mut Frame,
indent: usize,
) -> Block<BlockId<M::Id>> {
let (widget, cursor_row) = widgets::editor::<M>(frame, indent, &context.nick, &self.editor);
let cursor_row = cursor_row as i32; let cursor_row = cursor_row as i32;
Block::new(frame, BlockId::Cursor, widget).focus(cursor_row..cursor_row + 1) Block::new(frame, BlockId::Cursor, widget).focus(cursor_row..cursor_row + 1)
} }
fn pseudo_block(&self, nick: &str, frame: &mut Frame, indent: usize) -> Block<BlockId<M::Id>> { fn pseudo_block(
let widget = widgets::pseudo::<M>(indent, nick, &self.editor); &self,
context: &Context,
frame: &mut Frame,
indent: usize,
) -> Block<BlockId<M::Id>> {
let widget = widgets::pseudo::<M>(indent, &context.nick, &self.editor);
Block::new(frame, BlockId::Cursor, widget) Block::new(frame, BlockId::Cursor, widget)
} }
fn layout_subtree( fn layout_subtree(
&self, &self,
nick: &str, context: &Context,
frame: &mut Frame, frame: &mut Frame,
tree: &Tree<M>, tree: &Tree<M>,
indent: usize, indent: usize,
@ -99,7 +114,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
}; };
// Main message body // Main message body
let highlighted = self.cursor.refers_to(id); let highlighted = context.focused && self.cursor.refers_to(id);
let widget = if let Some(msg) = tree.msg(id) { let widget = if let Some(msg) = tree.msg(id) {
widgets::msg(highlighted, indent, msg, folded_info) widgets::msg(highlighted, indent, msg, folded_info)
} else { } else {
@ -112,7 +127,7 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
if !folded { if !folded {
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(nick, frame, tree, indent + 1, child, blocks); self.layout_subtree(context, frame, tree, indent + 1, child, blocks);
} }
} }
} }
@ -129,26 +144,31 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
Cursor::Editor { .. } => { Cursor::Editor { .. } => {
blocks blocks
.blocks_mut() .blocks_mut()
.push_back(self.editor_block(nick, frame, indent + 1)) .push_back(self.editor_block(context, frame, indent + 1))
} }
Cursor::Pseudo { .. } => { Cursor::Pseudo { .. } => {
blocks blocks
.blocks_mut() .blocks_mut()
.push_back(self.pseudo_block(nick, frame, indent + 1)) .push_back(self.pseudo_block(context, frame, indent + 1))
} }
_ => {} _ => {}
} }
} }
} }
fn layout_tree(&self, nick: &str, frame: &mut Frame, tree: Tree<M>) -> TreeBlocks<M::Id> { fn layout_tree(
&self,
context: &Context,
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(nick, frame, &tree, 0, tree.root(), &mut blocks); self.layout_subtree(context, frame, &tree, 0, tree.root(), &mut blocks);
blocks blocks
} }
fn layout_bottom(&self, nick: &str, frame: &mut Frame) -> TreeBlocks<M::Id> { fn layout_bottom(&self, context: &Context, 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
@ -166,17 +186,22 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} }
Cursor::Editor { parent: None, .. } => blocks Cursor::Editor { parent: None, .. } => blocks
.blocks_mut() .blocks_mut()
.push_back(self.editor_block(nick, frame, 0)), .push_back(self.editor_block(context, frame, 0)),
Cursor::Pseudo { parent: None, .. } => blocks Cursor::Pseudo { parent: None, .. } => blocks
.blocks_mut() .blocks_mut()
.push_back(self.pseudo_block(nick, frame, 0)), .push_back(self.pseudo_block(context, frame, 0)),
_ => {} _ => {}
} }
blocks blocks
} }
async fn expand_to_top(&self, nick: &str, frame: &mut Frame, blocks: &mut TreeBlocks<M::Id>) { async fn expand_to_top(
&self,
context: &Context,
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 {
@ -190,13 +215,13 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
None => break, None => break,
}; };
let prev_tree = self.store.tree(&prev_root_id).await; let prev_tree = self.store.tree(&prev_root_id).await;
blocks.prepend(self.layout_tree(nick, frame, prev_tree)); blocks.prepend(self.layout_tree(context, frame, prev_tree));
} }
} }
async fn expand_to_bottom( async fn expand_to_bottom(
&self, &self,
nick: &str, context: &Context,
frame: &mut Frame, frame: &mut Frame,
blocks: &mut TreeBlocks<M::Id>, blocks: &mut TreeBlocks<M::Id>,
) { ) {
@ -210,46 +235,46 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
}; };
if let Some(next_root_id) = next_root_id { if let Some(next_root_id) = next_root_id {
let next_tree = self.store.tree(&next_root_id).await; let next_tree = self.store.tree(&next_root_id).await;
blocks.append(self.layout_tree(nick, frame, next_tree)); blocks.append(self.layout_tree(context, frame, next_tree));
} else { } else {
blocks.append(self.layout_bottom(nick, frame)); blocks.append(self.layout_bottom(context, frame));
} }
} }
} }
async fn fill_screen_and_clamp_scrolling( async fn fill_screen_and_clamp_scrolling(
&self, &self,
nick: &str, context: &Context,
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(nick, frame, blocks).await; self.expand_to_top(context, 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(nick, frame, blocks).await; self.expand_to_bottom(context, 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(nick, frame, blocks).await; self.expand_to_top(context, frame, blocks).await;
} }
async fn layout_last_cursor_seed( async fn layout_last_cursor_seed(
&self, &self,
nick: &str, context: &Context,
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(nick, frame); let mut blocks = self.layout_bottom(context, 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);
@ -257,7 +282,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(nick, frame); let mut blocks = self.layout_bottom(context, frame);
blocks blocks
.blocks_mut() .blocks_mut()
@ -274,7 +299,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(nick, frame, tree); let mut blocks = self.layout_tree(context, frame, tree);
blocks blocks
.blocks_mut() .blocks_mut()
@ -287,7 +312,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, context: &Context,
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>,
@ -298,7 +323,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(nick, frame); let mut blocks = self.layout_bottom(context, frame);
blocks.blocks_mut().set_bottom_line(bottom_line); blocks.blocks_mut().set_bottom_line(bottom_line);
@ -313,7 +338,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(nick, frame, tree); let mut blocks = self.layout_tree(context, 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 };
@ -328,16 +353,16 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
async fn layout_initial_seed( async fn layout_initial_seed(
&self, &self,
nick: &str, context: &Context,
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(nick, frame, last_cursor_path, cursor_path) self.layout_cursor_seed(context, frame, last_cursor_path, cursor_path)
.await .await
} else { } else {
self.layout_last_cursor_seed(nick, frame, last_cursor_path) self.layout_last_cursor_seed(context, frame, last_cursor_path)
.await .await
} }
} }
@ -480,7 +505,12 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
result result
} }
pub async fn relayout(&mut self, nick: &str, frame: &mut Frame) -> TreeBlocks<M::Id> { pub async fn relayout(
&mut self,
nick: String,
focused: bool,
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
@ -499,29 +529,31 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
// //
// This entire process is complicated by the different kinds of cursors. // This entire process is complicated by the different kinds of cursors.
let context = Context { nick, focused };
let last_cursor_path = self.cursor_path(&self.last_cursor).await; let last_cursor_path = self.cursor_path(&self.last_cursor).await;
let cursor_path = self.cursor_path(&self.cursor).await; let cursor_path = self.cursor_path(&self.cursor).await;
self.make_path_visible(&cursor_path); self.make_path_visible(&cursor_path);
let mut blocks = self let mut blocks = self
.layout_initial_seed(nick, frame, &last_cursor_path, &cursor_path) .layout_initial_seed(&context, 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(nick, frame, &mut blocks) self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks)
.await; .await;
if !self.contains_cursor(&blocks) { if !self.contains_cursor(&blocks) {
blocks = self blocks = self
.layout_cursor_seed(nick, frame, &last_cursor_path, &cursor_path) .layout_cursor_seed(&context, frame, &last_cursor_path, &cursor_path)
.await; .await;
self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks) self.fill_screen_and_clamp_scrolling(&context, 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(nick, frame, &mut blocks) self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks)
.await; .await;
} }
Some(Correction::MoveCursorToVisibleArea) => { Some(Correction::MoveCursorToVisibleArea) => {
@ -539,15 +571,15 @@ impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
let last_cursor_path = self.store.path(&cursor_msg_id).await; let last_cursor_path = self.store.path(&cursor_msg_id).await;
blocks = self blocks = self
.layout_last_cursor_seed(nick, frame, &last_cursor_path) .layout_last_cursor_seed(&context, frame, &last_cursor_path)
.await; .await;
self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks) self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks)
.await; .await;
} }
} }
Some(Correction::CenterCursor) => { Some(Correction::CenterCursor) => {
self.scroll_so_cursor_is_centered(frame, &mut blocks); self.scroll_so_cursor_is_centered(frame, &mut blocks);
self.fill_screen_and_clamp_scrolling(nick, frame, &mut blocks) self.fill_screen_and_clamp_scrolling(&context, frame, &mut blocks)
.await; .await;
} }
None => {} None => {}

View file

@ -12,7 +12,7 @@ use crate::ui::widgets::list::{List, ListState};
use crate::ui::widgets::text::Text; use crate::ui::widgets::text::Text;
use crate::ui::widgets::BoxedWidget; use crate::ui::widgets::BoxedWidget;
pub fn widget(state: &ListState<String>, joined: &Joined, focus: bool) -> BoxedWidget { pub fn widget(state: &ListState<String>, joined: &Joined, focused: bool) -> BoxedWidget {
// TODO Handle focus // TODO Handle focus
let mut list = state.widget(); let mut list = state.widget();
render_rows(&mut list, joined); render_rows(&mut list, joined);