Port rendering logic

This commit is contained in:
Joscha 2022-07-19 21:47:40 +02:00
parent 583c82148f
commit 3af1193869
4 changed files with 110 additions and 98 deletions

View file

@ -2,7 +2,7 @@
mod blocks; mod blocks;
// mod cursor; // mod cursor;
mod layout; mod layout;
// mod render; mod render;
mod util; mod util;
use std::sync::Arc; use std::sync::Arc;
@ -14,7 +14,7 @@ use toss::frame::{Frame, Size};
use crate::store::{Msg, MsgStore}; use crate::store::{Msg, MsgStore};
use crate::ui::widgets::Widget; use crate::ui::widgets::Widget;
use self::blocks::Blocks; use self::blocks::{Block, BlockBody, Blocks, MarkerBlock};
/////////// ///////////
// State // // State //
@ -47,6 +47,21 @@ enum Cursor<I> {
Placeholder(LastChild<I>), Placeholder(LastChild<I>),
} }
impl<I: Eq> Cursor<I> {
fn matches_block(&self, block: &Block<I>) -> 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<M: Msg, S: MsgStore<M>> { struct InnerTreeViewState<M: Msg, S: MsgStore<M>> {
store: S, store: S,
last_blocks: Blocks<M::Id>, last_blocks: Blocks<M::Id>,
@ -104,7 +119,6 @@ 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.0.lock().await;
guard.relayout(frame).await; guard.relayout(frame).await;
// Draw layout to screen guard.draw_blocks(frame);
todo!()
} }
} }

View file

@ -7,21 +7,6 @@ use crate::store::{Msg, MsgStore, Path, Tree};
use super::blocks::{Block, BlockBody, Blocks, MarkerBlock, MsgBlock}; use super::blocks::{Block, BlockBody, Blocks, MarkerBlock, MsgBlock};
use super::{util, Cursor, InnerTreeViewState}; use super::{util, Cursor, InnerTreeViewState};
impl<I: Eq> Cursor<I> {
fn matches_block(&self, block: &Block<I>) -> 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<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> { impl<M: Msg, 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 {
@ -47,10 +32,10 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
) -> Option<&'a M::Id> { ) -> Option<&'a M::Id> {
match cursor { match cursor {
Cursor::Bottom => None, 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 { Cursor::Compose(lc) | Cursor::Placeholder(lc) => match &lc.after {
None => None, None => None,
Some(id) => Some(cursor_path.first()), Some(_) => Some(cursor_path.first()),
}, },
} }
} }
@ -80,7 +65,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
let nick = msg.nick(); let nick = msg.nick();
let content = msg.content(); 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 { if content_width < util::MIN_CONTENT_WIDTH as i32 {
Block::placeholder(Some(msg.time()), indent, msg.id()) Block::placeholder(Some(msg.time()), indent, msg.id())
} else { } else {

View file

@ -1,95 +1,107 @@
//! Rendering blocks to a [`Frame`]. //! Rendering blocks to a [`Frame`].
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use toss::frame::{Frame, Pos, Size}; use toss::frame::{Frame, Pos};
use toss::styled::Styled; use toss::styled::Styled;
use crate::store::Msg; use crate::store::{Msg, MsgStore};
use super::blocks::{Block, BlockBody, Blocks}; use super::blocks::{Block, BlockBody, MsgBlock, MsgContent};
use super::util::{ use super::{util, InnerTreeViewState};
self, style_indent, style_indent_inverted, style_placeholder, style_time, style_time_inverted,
INDENT, PLACEHOLDER, TIME_EMPTY, TIME_FORMAT,
};
use super::TreeView;
fn render_time(frame: &mut Frame, x: i32, y: i32, cursor: bool, time: Option<DateTime<Utc>>) { impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
let pos = Pos::new(x, y); fn render_time(frame: &mut Frame, line: i32, time: Option<DateTime<Utc>>, 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 { if let Some(time) = time {
style_time_inverted() let time = format!("{}", time.format(util::TIME_FORMAT));
} else { frame.write(pos, (&time, style));
style_time() } else {
}; frame.write(pos, (util::TIME_EMPTY, style));
}
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));
} }
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 mut styled = Styled::default();
let nick_pos = Pos::new(x + util::after_indent(indent), y); for _ in 0..indent {
let styled = Styled::new("[").and_then(nick).then("]"); styled = styled.then((util::INDENT, style));
frame.write(nick_pos, styled); }
}
fn render_block<M: Msg>(frame: &mut Frame, pos: Pos, size: Size, block: Block<M::Id>) { frame.write(pos, styled);
match block.body { }
BlockBody::Msg(msg) => {
let after_nick = util::after_nick(frame, block.indent, &msg.nick.text());
for (i, line) in msg.lines.into_iter().enumerate() { fn render_nick(frame: &mut Frame, line: i32, indent: usize, nick: Styled) {
let y = pos.y + block.line + i as i32; let nick_pos = Pos::new(util::after_indent(indent), line);
if y < pos.y || y >= pos.y + size.height as i32 { let styled = Styled::new("[").and_then(nick).then("]");
continue; frame.write(nick_pos, styled);
}
fn draw_msg_block(
frame: &mut Frame,
line: i32,
time: Option<DateTime<Utc>>,
indent: usize,
msg: &MsgBlock<M::Id>,
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 { MsgContent::Placeholder => {
render_indent(frame, pos.x, y, block.cursor, block.indent); Self::render_time(frame, line, time, is_cursor);
render_time(frame, pos.x, y, block.cursor, block.time); Self::render_indent(frame, line, indent, is_cursor);
render_nick(frame, pos.x, y, block.indent, msg.nick.clone()); let pos = Pos::new(util::after_indent(indent), line);
} else { frame.write(pos, (util::PLACEHOLDER, util::style_placeholder()));
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);
} }
} }
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<M: Msg> TreeView<M> { fn draw_block(frame: &mut Frame, block: &Block<M::Id>, is_cursor: bool) {
pub fn render_blocks(frame: &mut Frame, pos: Pos, size: Size, layout: Blocks<M::Id>) { match &block.body {
for block in layout.blocks { BlockBody::Marker(_) => {}
render_block::<M>(frame, pos, size, block); 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));
} }
} }
} }

View file

@ -2,6 +2,7 @@
use crossterm::style::{ContentStyle, Stylize}; use crossterm::style::{ContentStyle, Stylize};
use toss::frame::Frame; use toss::frame::Frame;
use toss::styled::Styled;
pub const TIME_FORMAT: &str = "%F %R "; pub const TIME_FORMAT: &str = "%F %R ";
pub const TIME_EMPTY: &str = " "; pub const TIME_EMPTY: &str = " ";
@ -38,6 +39,6 @@ pub fn after_indent(indent: usize) -> i32 {
(TIME_WIDTH + indent * INDENT_WIDTH) as i32 (TIME_WIDTH + indent * INDENT_WIDTH) as i32
} }
pub fn after_nick(frame: &mut Frame, indent: usize, nick: &str) -> i32 { pub fn after_nick(frame: &mut Frame, indent: usize, nick: &Styled) -> i32 {
after_indent(indent) + 1 + frame.width(nick) as i32 + 2 after_indent(indent) + 1 + frame.width_styled(nick) as i32 + 2
} }