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 euph;
|
||||||
mod export;
|
mod export;
|
||||||
mod logger;
|
mod logger;
|
||||||
|
mod macros;
|
||||||
mod replies;
|
mod replies;
|
||||||
mod store;
|
mod store;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// mod action;
|
// mod action;
|
||||||
// mod blocks;
|
mod blocks;
|
||||||
// mod cursor;
|
// mod cursor;
|
||||||
// mod layout;
|
// mod layout;
|
||||||
// mod render;
|
// 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 std::collections::VecDeque;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use toss::styled::Styled;
|
use toss::styled::Styled;
|
||||||
|
|
||||||
use super::{util, Cursor};
|
use crate::macros::some_or_return;
|
||||||
|
|
||||||
pub struct Block<I> {
|
pub enum MarkerBlock<I> {
|
||||||
pub id: I,
|
After(I),
|
||||||
pub line: i32,
|
Bottom,
|
||||||
pub height: i32,
|
|
||||||
pub cursor: bool,
|
|
||||||
pub time: Option<DateTime<Utc>>,
|
|
||||||
pub indent: usize,
|
|
||||||
pub body: BlockBody,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I> Block<I> {
|
pub enum MsgContent {
|
||||||
pub fn msg(
|
Msg { nick: Styled, lines: Vec<Styled> },
|
||||||
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),
|
|
||||||
Placeholder,
|
Placeholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MsgBlock {
|
pub struct MsgBlock<I> {
|
||||||
pub nick: Styled,
|
id: I,
|
||||||
pub lines: Vec<Styled>,
|
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.
|
/// Pre-layouted messages as a sequence of blocks.
|
||||||
///
|
///
|
||||||
/// These blocks are straightforward to render, but also provide a level of
|
/// These blocks are straightforward to render, but also provide a level of
|
||||||
/// abstraction between the layouting and actual displaying of messages. This
|
/// abstraction between the layouting and actual displaying of messages.
|
||||||
/// might be useful in the future to ensure the cursor is always on a visible
|
|
||||||
/// message, for example.
|
|
||||||
///
|
///
|
||||||
/// The following equation describes the relationship between the
|
/// The following equation describes the relationship between the
|
||||||
/// [`Blocks::top_line`] and [`Blocks::bottom_line`] fields:
|
/// [`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
|
/// 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
|
/// always the last line in a nonempty [`Blocks`]. In an empty layout, the
|
||||||
/// equation simplifies to
|
/// equation simplifies to
|
||||||
///
|
///
|
||||||
/// `top_line = bottom_line + 1`
|
/// `bottom_line = top_line - 1`
|
||||||
pub struct Blocks<I> {
|
pub struct Blocks<I> {
|
||||||
pub blocks: VecDeque<Block<I>>,
|
pub blocks: VecDeque<Block<I>>,
|
||||||
/// The top line of the first block. Useful for prepending blocks,
|
/// The top line of the first block. Useful for prepending blocks,
|
||||||
|
|
@ -90,66 +82,75 @@ pub struct Blocks<I> {
|
||||||
pub bottom_line: i32,
|
pub bottom_line: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I: PartialEq> Blocks<I> {
|
impl<I> Blocks<I> {
|
||||||
pub fn new() -> Self {
|
pub fn new(initial: Block<I>) -> Self {
|
||||||
Self::new_below(0)
|
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
|
pub fn new_empty() -> Self {
|
||||||
/// in `top_line = bottom_line = line`.
|
|
||||||
pub fn new_below(line: i32) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
blocks: VecDeque::new(),
|
blocks: VecDeque::new(),
|
||||||
top_line: line + 1,
|
top_line: 0,
|
||||||
bottom_line: line,
|
bottom_line: -1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mark_cursor(&mut self, id: &I) -> usize {
|
pub fn find<F>(&self, f: F) -> Option<&Block<I>>
|
||||||
let mut cursor = None;
|
where
|
||||||
for (i, block) in self.blocks.iter_mut().enumerate() {
|
F: Fn(&Block<I>) -> bool,
|
||||||
if &block.id == id {
|
{
|
||||||
block.cursor = true;
|
self.blocks.iter().find(|b| f(b))
|
||||||
if cursor.is_some() {
|
|
||||||
panic!("more than one cursor in blocks");
|
|
||||||
}
|
|
||||||
cursor = Some(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor.expect("no cursor in blocks")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_offsets_with_cursor(&mut self, cursor: &Cursor<I>, height: u16) {
|
fn find_index_and_line<F>(&self, f: F) -> Option<(usize, i32)>
|
||||||
let cursor_index = self.mark_cursor(&cursor.id);
|
where
|
||||||
let cursor_line = util::proportion_to_line(height, cursor.proportion);
|
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
|
/// Update the offsets such that the line of the first block with a `Some`
|
||||||
self.blocks[cursor_index].line = cursor_line;
|
/// return value becomes that value.
|
||||||
for i in (0..cursor_index).rev() {
|
pub fn recalculate_offsets<F>(&mut self, f: F)
|
||||||
// let succ_line = self.0[i + 1].line;
|
where
|
||||||
// let curr = &mut self.0[i];
|
F: Fn(&Block<I>) -> Option<i32>,
|
||||||
// curr.line = succ_line - curr.height;
|
{
|
||||||
self.blocks[i].line = self.blocks[i + 1].line - self.blocks[i].height;
|
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() {
|
for i in (idx + 1)..self.blocks.len() {
|
||||||
// let pred = &self.0[i - 1];
|
self.blocks[i].line = self.blocks[i - 1].line + self.blocks[i - 1].height();
|
||||||
// self.0[i].line = pred.line + pred.height;
|
|
||||||
self.blocks[i].line = self.blocks[i - 1].line + self.blocks[i - 1].height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.top_line = self.blocks.front().expect("blocks nonempty").line;
|
self.top_line = self.blocks.front().expect("blocks nonempty").line;
|
||||||
let bottom = self.blocks.back().expect("blocks nonempty");
|
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>) {
|
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;
|
block.line = self.top_line;
|
||||||
self.blocks.push_front(block);
|
self.blocks.push_front(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_back(&mut self, mut block: Block<I>) {
|
pub fn push_back(&mut self, mut block: Block<I>) {
|
||||||
block.line = self.bottom_line + 1;
|
block.line = self.bottom_line + 1;
|
||||||
self.bottom_line += block.height;
|
self.bottom_line += block.height();
|
||||||
self.blocks.push_back(block);
|
self.blocks.push_back(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,8 +173,4 @@ impl<I: PartialEq> Blocks<I> {
|
||||||
block.line += delta;
|
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