Adapt blocks to include markers
This commit is contained in:
parent
26e988114c
commit
21d908874d
4 changed files with 112 additions and 99 deletions
15
src/macros.rs
Normal file
15
src/macros.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
macro_rules! some_or_return {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
None => return,
|
||||
Some(result) => result,
|
||||
}
|
||||
};
|
||||
($e:expr, $ret:expr) => {
|
||||
match $e {
|
||||
None => return $ret,
|
||||
Some(result) => result,
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use some_or_return;
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
mod euph;
|
||||
mod export;
|
||||
mod logger;
|
||||
mod macros;
|
||||
mod replies;
|
||||
mod store;
|
||||
mod ui;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// mod action;
|
||||
// mod blocks;
|
||||
mod blocks;
|
||||
// mod cursor;
|
||||
// mod layout;
|
||||
// mod render;
|
||||
|
|
|
|||
|
|
@ -1,85 +1,77 @@
|
|||
//! Intermediate representation of messages as blocks of lines.
|
||||
//! Intermediate representation of chat history as blocks of things.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use toss::styled::Styled;
|
||||
|
||||
use super::{util, Cursor};
|
||||
use crate::macros::some_or_return;
|
||||
|
||||
pub struct Block<I> {
|
||||
pub id: I,
|
||||
pub line: i32,
|
||||
pub height: i32,
|
||||
pub cursor: bool,
|
||||
pub time: Option<DateTime<Utc>>,
|
||||
pub indent: usize,
|
||||
pub body: BlockBody,
|
||||
pub enum MarkerBlock<I> {
|
||||
After(I),
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl<I> Block<I> {
|
||||
pub fn msg(
|
||||
id: I,
|
||||
indent: usize,
|
||||
time: DateTime<Utc>,
|
||||
nick: Styled,
|
||||
lines: Vec<Styled>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
line: 0,
|
||||
height: lines.len() as i32,
|
||||
indent,
|
||||
time: Some(time),
|
||||
cursor: false,
|
||||
body: BlockBody::Msg(MsgBlock { nick, lines }),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn placeholder(id: I, indent: usize) -> Self {
|
||||
Self {
|
||||
id,
|
||||
line: 0,
|
||||
height: 1,
|
||||
indent,
|
||||
time: None,
|
||||
cursor: false,
|
||||
body: BlockBody::Placeholder,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn time(mut self, time: DateTime<Utc>) -> Self {
|
||||
self.time = Some(time);
|
||||
self
|
||||
}
|
||||
}
|
||||
pub enum BlockBody {
|
||||
Msg(MsgBlock),
|
||||
pub enum MsgContent {
|
||||
Msg { nick: Styled, lines: Vec<Styled> },
|
||||
Placeholder,
|
||||
}
|
||||
|
||||
pub struct MsgBlock {
|
||||
pub nick: Styled,
|
||||
pub lines: Vec<Styled>,
|
||||
pub struct MsgBlock<I> {
|
||||
id: I,
|
||||
cursor: bool,
|
||||
content: MsgContent,
|
||||
}
|
||||
|
||||
impl<I> MsgBlock<I> {
|
||||
pub fn height(&self) -> i32 {
|
||||
match &self.content {
|
||||
MsgContent::Msg { lines, .. } => lines.len() as i32,
|
||||
MsgContent::Placeholder => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ComposeBlock {
|
||||
// TODO Editor widget
|
||||
}
|
||||
|
||||
pub enum BlockBody<I> {
|
||||
Marker(MarkerBlock<I>),
|
||||
Msg(MsgBlock<I>),
|
||||
Compose(ComposeBlock),
|
||||
}
|
||||
|
||||
pub struct Block<I> {
|
||||
line: i32,
|
||||
indent: usize,
|
||||
body: BlockBody<I>,
|
||||
}
|
||||
|
||||
impl<I> Block<I> {
|
||||
pub fn height(&self) -> i32 {
|
||||
match &self.body {
|
||||
BlockBody::Marker(m) => 0,
|
||||
BlockBody::Msg(m) => m.height(),
|
||||
BlockBody::Compose(e) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pre-layouted messages as a sequence of blocks.
|
||||
///
|
||||
/// These blocks are straightforward to render, but also provide a level of
|
||||
/// abstraction between the layouting and actual displaying of messages. This
|
||||
/// might be useful in the future to ensure the cursor is always on a visible
|
||||
/// message, for example.
|
||||
/// abstraction between the layouting and actual displaying of messages.
|
||||
///
|
||||
/// The following equation describes the relationship between the
|
||||
/// [`Blocks::top_line`] and [`Blocks::bottom_line`] fields:
|
||||
///
|
||||
/// `bottom_line - top_line + 1 = sum of all heights`
|
||||
/// `bottom_line - top_line = sum of all heights - 1`
|
||||
///
|
||||
/// This ensures that `top_line` is always the first line and `bottom_line` is
|
||||
/// always the last line in a nonempty [`Blocks`]. In an empty layout, the
|
||||
/// equation simplifies to
|
||||
///
|
||||
/// `top_line = bottom_line + 1`
|
||||
/// `bottom_line = top_line - 1`
|
||||
pub struct Blocks<I> {
|
||||
pub blocks: VecDeque<Block<I>>,
|
||||
/// The top line of the first block. Useful for prepending blocks,
|
||||
|
|
@ -90,66 +82,75 @@ pub struct Blocks<I> {
|
|||
pub bottom_line: i32,
|
||||
}
|
||||
|
||||
impl<I: PartialEq> Blocks<I> {
|
||||
pub fn new() -> Self {
|
||||
Self::new_below(0)
|
||||
impl<I> Blocks<I> {
|
||||
pub fn new(initial: Block<I>) -> Self {
|
||||
let top_line = initial.line;
|
||||
let bottom_line = top_line + initial.height() - 1;
|
||||
let mut blocks = VecDeque::new();
|
||||
blocks.push_back(initial);
|
||||
Self {
|
||||
blocks,
|
||||
top_line,
|
||||
bottom_line,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`Blocks`] such that prepending a single line will result
|
||||
/// in `top_line = bottom_line = line`.
|
||||
pub fn new_below(line: i32) -> Self {
|
||||
pub fn new_empty() -> Self {
|
||||
Self {
|
||||
blocks: VecDeque::new(),
|
||||
top_line: line + 1,
|
||||
bottom_line: line,
|
||||
top_line: 0,
|
||||
bottom_line: -1,
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_cursor(&mut self, id: &I) -> usize {
|
||||
let mut cursor = None;
|
||||
for (i, block) in self.blocks.iter_mut().enumerate() {
|
||||
if &block.id == id {
|
||||
block.cursor = true;
|
||||
if cursor.is_some() {
|
||||
panic!("more than one cursor in blocks");
|
||||
}
|
||||
cursor = Some(i);
|
||||
}
|
||||
}
|
||||
cursor.expect("no cursor in blocks")
|
||||
pub fn find<F>(&self, f: F) -> Option<&Block<I>>
|
||||
where
|
||||
F: Fn(&Block<I>) -> bool,
|
||||
{
|
||||
self.blocks.iter().find(|b| f(b))
|
||||
}
|
||||
|
||||
pub fn calculate_offsets_with_cursor(&mut self, cursor: &Cursor<I>, height: u16) {
|
||||
let cursor_index = self.mark_cursor(&cursor.id);
|
||||
let cursor_line = util::proportion_to_line(height, cursor.proportion);
|
||||
fn find_index_and_line<F>(&self, f: F) -> Option<(usize, i32)>
|
||||
where
|
||||
F: Fn(&Block<I>) -> Option<i32>,
|
||||
{
|
||||
self.blocks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, b)| f(b).map(|l| (i, l)))
|
||||
}
|
||||
|
||||
// Propagate lines from cursor to both ends
|
||||
self.blocks[cursor_index].line = cursor_line;
|
||||
for i in (0..cursor_index).rev() {
|
||||
// let succ_line = self.0[i + 1].line;
|
||||
// let curr = &mut self.0[i];
|
||||
// curr.line = succ_line - curr.height;
|
||||
self.blocks[i].line = self.blocks[i + 1].line - self.blocks[i].height;
|
||||
/// Update the offsets such that the line of the first block with a `Some`
|
||||
/// return value becomes that value.
|
||||
pub fn recalculate_offsets<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(&Block<I>) -> Option<i32>,
|
||||
{
|
||||
let (idx, line) = some_or_return!(self.find_index_and_line(f));
|
||||
|
||||
// Propagate lines from index to both ends
|
||||
self.blocks[idx].line = line;
|
||||
for i in (0..idx).rev() {
|
||||
self.blocks[i].line = self.blocks[i + 1].line - self.blocks[i].height();
|
||||
}
|
||||
for i in (cursor_index + 1)..self.blocks.len() {
|
||||
// let pred = &self.0[i - 1];
|
||||
// self.0[i].line = pred.line + pred.height;
|
||||
self.blocks[i].line = self.blocks[i - 1].line + self.blocks[i - 1].height;
|
||||
for i in (idx + 1)..self.blocks.len() {
|
||||
self.blocks[i].line = self.blocks[i - 1].line + self.blocks[i - 1].height();
|
||||
}
|
||||
|
||||
self.top_line = self.blocks.front().expect("blocks nonempty").line;
|
||||
let bottom = self.blocks.back().expect("blocks nonempty");
|
||||
self.bottom_line = bottom.line + bottom.height - 1;
|
||||
self.bottom_line = bottom.line + bottom.height() - 1;
|
||||
}
|
||||
|
||||
pub fn push_front(&mut self, mut block: Block<I>) {
|
||||
self.top_line -= block.height;
|
||||
self.top_line -= block.height();
|
||||
block.line = self.top_line;
|
||||
self.blocks.push_front(block);
|
||||
}
|
||||
|
||||
pub fn push_back(&mut self, mut block: Block<I>) {
|
||||
block.line = self.bottom_line + 1;
|
||||
self.bottom_line += block.height;
|
||||
self.bottom_line += block.height();
|
||||
self.blocks.push_back(block);
|
||||
}
|
||||
|
||||
|
|
@ -172,8 +173,4 @@ impl<I: PartialEq> Blocks<I> {
|
|||
block.line += delta;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find(&self, id: &I) -> Option<&Block<I>> {
|
||||
self.blocks.iter().find(|b| &b.id == id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue