diff --git a/src/ui/chat/tree.rs b/src/ui/chat/tree.rs index ed31ca7..c026a42 100644 --- a/src/ui/chat/tree.rs +++ b/src/ui/chat/tree.rs @@ -2,7 +2,7 @@ mod blocks; // mod cursor; mod layout; -// mod render; +mod render; mod util; use std::sync::Arc; @@ -14,7 +14,7 @@ use toss::frame::{Frame, Size}; use crate::store::{Msg, MsgStore}; use crate::ui::widgets::Widget; -use self::blocks::Blocks; +use self::blocks::{Block, BlockBody, Blocks, MarkerBlock}; /////////// // State // @@ -47,6 +47,21 @@ enum Cursor { Placeholder(LastChild), } +impl Cursor { + fn matches_block(&self, block: &Block) -> bool { + match self { + Self::Bottom => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), + Self::Msg(id) => matches!(&block.body, BlockBody::Msg(msg) if msg.id == *id), + Self::Compose(lc) | Self::Placeholder(lc) => match &lc.after { + Some(bid) => { + matches!(&block.body, BlockBody::Marker(MarkerBlock::After(aid)) if aid == bid) + } + None => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), + }, + } + } +} + struct InnerTreeViewState> { store: S, last_blocks: Blocks, @@ -104,7 +119,6 @@ where async fn render(self: Box, frame: &mut Frame) { let mut guard = self.0.lock().await; guard.relayout(frame).await; - // Draw layout to screen - todo!() + guard.draw_blocks(frame); } } diff --git a/src/ui/chat/tree/layout.rs b/src/ui/chat/tree/layout.rs index 3700c38..45c31f2 100644 --- a/src/ui/chat/tree/layout.rs +++ b/src/ui/chat/tree/layout.rs @@ -7,21 +7,6 @@ use crate::store::{Msg, MsgStore, Path, Tree}; use super::blocks::{Block, BlockBody, Blocks, MarkerBlock, MsgBlock}; use super::{util, Cursor, InnerTreeViewState}; -impl Cursor { - fn matches_block(&self, block: &Block) -> bool { - match self { - Self::Bottom => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), - Self::Msg(id) => matches!(&block.body, BlockBody::Msg(msg) if msg.id == *id), - Self::Compose(lc) | Self::Placeholder(lc) => match &lc.after { - Some(bid) => { - matches!(&block.body, BlockBody::Marker(MarkerBlock::After(aid)) if aid == bid) - } - None => matches!(&block.body, BlockBody::Marker(MarkerBlock::Bottom)), - }, - } - } -} - impl> InnerTreeViewState { async fn cursor_path(&self, cursor: &Cursor) -> Path { match cursor { @@ -47,10 +32,10 @@ impl> InnerTreeViewState { ) -> Option<&'a M::Id> { match cursor { Cursor::Bottom => None, - Cursor::Msg(id) => Some(cursor_path.first()), + Cursor::Msg(_) => Some(cursor_path.first()), Cursor::Compose(lc) | Cursor::Placeholder(lc) => match &lc.after { None => None, - Some(id) => Some(cursor_path.first()), + Some(_) => Some(cursor_path.first()), }, } } @@ -80,7 +65,7 @@ impl> InnerTreeViewState { let nick = msg.nick(); let content = msg.content(); - let content_width = size.width as i32 - util::after_nick(frame, indent, &nick.text()); + let content_width = size.width as i32 - util::after_nick(frame, indent, &nick); if content_width < util::MIN_CONTENT_WIDTH as i32 { Block::placeholder(Some(msg.time()), indent, msg.id()) } else { diff --git a/src/ui/chat/tree/render.rs b/src/ui/chat/tree/render.rs index f1eec92..08f81ea 100644 --- a/src/ui/chat/tree/render.rs +++ b/src/ui/chat/tree/render.rs @@ -1,95 +1,107 @@ //! Rendering blocks to a [`Frame`]. use chrono::{DateTime, Utc}; -use toss::frame::{Frame, Pos, Size}; +use toss::frame::{Frame, Pos}; use toss::styled::Styled; -use crate::store::Msg; +use crate::store::{Msg, MsgStore}; -use super::blocks::{Block, BlockBody, Blocks}; -use super::util::{ - self, style_indent, style_indent_inverted, style_placeholder, style_time, style_time_inverted, - INDENT, PLACEHOLDER, TIME_EMPTY, TIME_FORMAT, -}; -use super::TreeView; +use super::blocks::{Block, BlockBody, MsgBlock, MsgContent}; +use super::{util, InnerTreeViewState}; -fn render_time(frame: &mut Frame, x: i32, y: i32, cursor: bool, time: Option>) { - let pos = Pos::new(x, y); +impl> InnerTreeViewState { + fn render_time(frame: &mut Frame, line: i32, time: Option>, is_cursor: bool) { + let pos = Pos::new(0, line); + let style = if is_cursor { + util::style_time_inverted() + } else { + util::style_time() + }; - let style = if cursor { - style_time_inverted() - } else { - style_time() - }; - - if let Some(time) = time { - let time = format!("{}", time.format(TIME_FORMAT)); - frame.write(pos, (&time, style)); - } else { - frame.write(pos, (TIME_EMPTY, style)); - } -} - -fn render_indent(frame: &mut Frame, x: i32, y: i32, cursor: bool, indent: usize) { - let style = if cursor { - style_indent_inverted() - } else { - style_indent() - }; - - let mut styled = Styled::default(); - for _ in 0..indent { - styled = styled.then((INDENT, style)); + if let Some(time) = time { + let time = format!("{}", time.format(util::TIME_FORMAT)); + frame.write(pos, (&time, style)); + } else { + frame.write(pos, (util::TIME_EMPTY, style)); + } } - frame.write(Pos::new(x + util::after_indent(0), y), styled); -} + fn render_indent(frame: &mut Frame, line: i32, indent: usize, is_cursor: bool) { + let pos = Pos::new(util::after_indent(0), line); + let style = if is_cursor { + util::style_indent_inverted() + } else { + util::style_indent() + }; -fn render_nick(frame: &mut Frame, x: i32, y: i32, indent: usize, nick: Styled) { - let nick_pos = Pos::new(x + util::after_indent(indent), y); - let styled = Styled::new("[").and_then(nick).then("]"); - frame.write(nick_pos, styled); -} + let mut styled = Styled::default(); + for _ in 0..indent { + styled = styled.then((util::INDENT, style)); + } -fn render_block(frame: &mut Frame, pos: Pos, size: Size, block: Block) { - match block.body { - BlockBody::Msg(msg) => { - let after_nick = util::after_nick(frame, block.indent, &msg.nick.text()); + frame.write(pos, styled); + } - for (i, line) in msg.lines.into_iter().enumerate() { - let y = pos.y + block.line + i as i32; - if y < pos.y || y >= pos.y + size.height as i32 { - continue; + fn render_nick(frame: &mut Frame, line: i32, indent: usize, nick: Styled) { + let nick_pos = Pos::new(util::after_indent(indent), line); + let styled = Styled::new("[").and_then(nick).then("]"); + frame.write(nick_pos, styled); + } + + fn draw_msg_block( + frame: &mut Frame, + line: i32, + time: Option>, + indent: usize, + msg: &MsgBlock, + is_cursor: bool, + ) { + match &msg.content { + MsgContent::Msg { nick, lines } => { + let height: i32 = frame.size().height.into(); + let after_nick = util::after_nick(frame, indent, nick); + + for (i, text) in lines.iter().enumerate() { + let line = line + i as i32; + if line < 0 || line >= height { + continue; + } + + if i == 0 { + Self::render_indent(frame, line, indent, is_cursor); + Self::render_time(frame, line, time, is_cursor); + Self::render_nick(frame, line, indent, nick.clone()); + } else { + Self::render_indent(frame, line, indent + 1, false); + Self::render_indent(frame, line, indent, is_cursor); + Self::render_time(frame, line, None, is_cursor); + } + + frame.write(Pos::new(after_nick, line), text.clone()); } - - if i == 0 { - render_indent(frame, pos.x, y, block.cursor, block.indent); - render_time(frame, pos.x, y, block.cursor, block.time); - render_nick(frame, pos.x, y, block.indent, msg.nick.clone()); - } else { - render_indent(frame, pos.x, y, false, block.indent + 1); - render_indent(frame, pos.x, y, block.cursor, block.indent); - render_time(frame, pos.x, y, block.cursor, None); - } - - let line_pos = Pos::new(pos.x + after_nick, y); - frame.write(line_pos, line); + } + MsgContent::Placeholder => { + Self::render_time(frame, line, time, is_cursor); + Self::render_indent(frame, line, indent, is_cursor); + let pos = Pos::new(util::after_indent(indent), line); + frame.write(pos, (util::PLACEHOLDER, util::style_placeholder())); } } - BlockBody::Placeholder => { - let y = pos.y + block.line; - render_time(frame, pos.x, y, block.cursor, block.time); - render_indent(frame, pos.x, y, block.cursor, block.indent); - let pos = Pos::new(pos.x + util::after_indent(block.indent), y); - frame.write(pos, (PLACEHOLDER, style_placeholder())); - } } -} -impl TreeView { - pub fn render_blocks(frame: &mut Frame, pos: Pos, size: Size, layout: Blocks) { - for block in layout.blocks { - render_block::(frame, pos, size, block); + fn draw_block(frame: &mut Frame, block: &Block, is_cursor: bool) { + match &block.body { + BlockBody::Marker(_) => {} + BlockBody::Msg(msg) => { + Self::draw_msg_block(frame, block.line, block.time, block.indent, msg, is_cursor) + } + BlockBody::Compose(_) => {} + } + } + + pub fn draw_blocks(&self, frame: &mut Frame) { + for block in self.last_blocks.iter() { + Self::draw_block(frame, block, self.cursor.matches_block(block)); } } } diff --git a/src/ui/chat/tree/util.rs b/src/ui/chat/tree/util.rs index 4ee26e5..0b7069b 100644 --- a/src/ui/chat/tree/util.rs +++ b/src/ui/chat/tree/util.rs @@ -2,6 +2,7 @@ use crossterm::style::{ContentStyle, Stylize}; use toss::frame::Frame; +use toss::styled::Styled; pub const TIME_FORMAT: &str = "%F %R "; pub const TIME_EMPTY: &str = " "; @@ -38,6 +39,6 @@ pub fn after_indent(indent: usize) -> i32 { (TIME_WIDTH + indent * INDENT_WIDTH) as i32 } -pub fn after_nick(frame: &mut Frame, indent: usize, nick: &str) -> i32 { - after_indent(indent) + 1 + frame.width(nick) as i32 + 2 +pub fn after_nick(frame: &mut Frame, indent: usize, nick: &Styled) -> i32 { + after_indent(indent) + 1 + frame.width_styled(nick) as i32 + 2 }