Start working on tree layouting
This commit is contained in:
parent
6fdce9db1e
commit
021d5a8943
4 changed files with 159 additions and 49 deletions
|
|
@ -57,9 +57,10 @@ impl<M: Msg, S: MsgStore<M>> Chat<M, S> {
|
|||
|
||||
pub fn render(&mut self, frame: &mut Frame, pos: Pos, size: Size) {
|
||||
match self.mode {
|
||||
Mode::Tree => self
|
||||
.tree
|
||||
.render(&mut self.store, &self.room, frame, pos, size),
|
||||
Mode::Tree => {
|
||||
self.tree
|
||||
.render(&mut self.store, &self.room, &self.cursor, frame, pos, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crossterm::event::KeyEvent;
|
|||
use crossterm::style::ContentStyle;
|
||||
use toss::frame::{Frame, Pos, Size};
|
||||
|
||||
use crate::store::{Msg, MsgStore};
|
||||
use crate::store::{Msg, MsgStore, Tree};
|
||||
|
||||
use super::Cursor;
|
||||
|
||||
|
|
@ -30,61 +30,97 @@ struct MsgBlock {
|
|||
content: Vec<String>,
|
||||
}
|
||||
|
||||
struct Layout<I>(VecDeque<Block<I>>);
|
||||
/// 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.
|
||||
///
|
||||
/// The following equation describes the relationship between the
|
||||
/// [`Layout::top_line`] and [`Layout::bottom_line`] fields:
|
||||
///
|
||||
/// `bottom_line - top_line + 1 = sum of all heights`
|
||||
///
|
||||
/// This ensures that `top_line` is always the first line and `bottom_line` is
|
||||
/// always the last line in a nonempty [`Layout`]. In an empty layout, the
|
||||
/// equation simplifies to
|
||||
///
|
||||
/// `top_line = bottom_line + 1`
|
||||
struct Layout<I> {
|
||||
blocks: VecDeque<Block<I>>,
|
||||
/// The top line of the first block. Useful for prepending blocks,
|
||||
/// especially to empty [`Layout`]s.
|
||||
top_line: i32,
|
||||
/// The bottom line of the last block. Useful for appending blocks,
|
||||
/// especially to empty [`Layout`]s.
|
||||
bottom_line: i32,
|
||||
}
|
||||
|
||||
impl<I: PartialEq> Layout<I> {
|
||||
pub fn new() -> Self {
|
||||
Self(VecDeque::new())
|
||||
fn new() -> Self {
|
||||
Self::new_below(0)
|
||||
}
|
||||
|
||||
fn mark_cursor(&mut self, id: &I) {
|
||||
for block in &mut self.0 {
|
||||
if block.id.as_ref() == Some(id) {
|
||||
block.cursor = true;
|
||||
}
|
||||
/// Create a new [`Layout`] such that prepending a single line will result
|
||||
/// in `top_line = bottom_line = line`.
|
||||
fn new_below(line: i32) -> Self {
|
||||
Self {
|
||||
blocks: VecDeque::new(),
|
||||
top_line: line + 1,
|
||||
bottom_line: line,
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_offsets_with_cursor(&mut self, line: i32) {
|
||||
let cursor_index = self
|
||||
.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, b)| b.cursor)
|
||||
.expect("layout must contain cursor block")
|
||||
.0;
|
||||
fn mark_cursor(&mut self, id: &I) -> usize {
|
||||
let mut cursor = None;
|
||||
for (i, block) in self.blocks.iter_mut().enumerate() {
|
||||
if block.id.as_ref() == Some(id) {
|
||||
block.cursor = true;
|
||||
if cursor.is_some() {
|
||||
panic!("more than one cursor in layout");
|
||||
}
|
||||
cursor = Some(i);
|
||||
}
|
||||
}
|
||||
cursor.expect("no cursor in layout")
|
||||
}
|
||||
|
||||
fn calculate_offsets_with_cursor(&mut self, cursor: &Cursor<I>, height: i32) {
|
||||
let cursor_index = self.mark_cursor(&cursor.id);
|
||||
let cursor_line = ((height - 1) as f32 * cursor.proportion).round() as i32;
|
||||
|
||||
// Propagate lines from cursor to both ends
|
||||
self.0[cursor_index].line = line;
|
||||
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.0[i].line = self.0[i + 1].line - self.0[i].height;
|
||||
self.blocks[i].line = self.blocks[i + 1].line - self.blocks[i].height;
|
||||
}
|
||||
for i in (cursor_index + 1)..self.0.len() {
|
||||
for i in (cursor_index + 1)..self.blocks.len() {
|
||||
// let pred = &self.0[i - 1];
|
||||
// self.0[i].line = pred.line + pred.height;
|
||||
self.0[i].line = self.0[i - 1].line + self.0[i - 1].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;
|
||||
let bottom = self.blocks.back().expect("blocks nonempty");
|
||||
self.bottom_line = bottom.line + bottom.height - 1;
|
||||
}
|
||||
|
||||
fn prepend(&mut self, mut layout: Self) {
|
||||
while let Some(mut block) = layout.blocks.pop_back() {
|
||||
self.top_line -= block.height;
|
||||
block.line = self.top_line;
|
||||
self.blocks.push_front(block);
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_offsets_without_cursor(&mut self, height: i32) {
|
||||
if let Some(back) = self.0.back_mut() {
|
||||
back.line = height - back.height;
|
||||
}
|
||||
for i in (0..self.0.len() - 1).rev() {
|
||||
self.0[i].line = self.0[i + 1].line - self.0[i].height;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_offsets(&mut self, height: i32, cursor: Option<Cursor<I>>) {
|
||||
if let Some(cursor) = cursor {
|
||||
let line = ((height - 1) as f32 * cursor.proportion) as i32;
|
||||
self.mark_cursor(&cursor.id);
|
||||
self.calculate_offsets_with_cursor(line);
|
||||
} else {
|
||||
self.calculate_offsets_without_cursor(height);
|
||||
fn append(&mut self, mut layout: Self) {
|
||||
while let Some(mut block) = layout.blocks.pop_front() {
|
||||
block.line = self.bottom_line + 1;
|
||||
self.bottom_line += block.height;
|
||||
self.blocks.push_back(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -103,24 +139,49 @@ impl<M: Msg> TreeView<M> {
|
|||
}
|
||||
}
|
||||
|
||||
fn layout_tree(tree: Tree<M>) -> Layout<M::Id> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn layout<S: MsgStore<M>>(
|
||||
&mut self,
|
||||
room: &str,
|
||||
store: S,
|
||||
cursor: &mut Option<Cursor<M::Id>>,
|
||||
cursor: &Option<Cursor<M::Id>>,
|
||||
size: Size,
|
||||
) -> Layout<M::Id> {
|
||||
let height: i32 = size.height.into();
|
||||
if let Some(cursor) = cursor {
|
||||
// TODO Ensure focus lies on cursor path, otherwise unfocus
|
||||
// TODO Unfold all messages on path to cursor
|
||||
|
||||
// Produce layout of cursor subtree (with correct offsets)
|
||||
let cursor_path = store.path(room, &cursor.id).await;
|
||||
// TODO Produce layout of cursor subtree (with correct offsets)
|
||||
let cursor_tree = store.tree(room, cursor_path.first()).await;
|
||||
let mut layout = Self::layout_tree(cursor_tree);
|
||||
layout.calculate_offsets_with_cursor(cursor, height);
|
||||
|
||||
// TODO Expand layout upwards and downwards if there is no focus
|
||||
todo!()
|
||||
} else {
|
||||
// TODO Ensure there is no focus
|
||||
// TODO Produce layout of last tree (with correct offsets)
|
||||
// TODO Expand layout upwards
|
||||
todo!()
|
||||
|
||||
// Start layout at the bottom of the screen
|
||||
let mut layout = Layout::new_below(height);
|
||||
|
||||
// Expand layout upwards until the edge of the screen
|
||||
let mut tree_id = store.last_tree(room).await;
|
||||
while layout.top_line > 0 {
|
||||
if let Some(actual_tree_id) = &tree_id {
|
||||
let tree = store.tree(room, actual_tree_id).await;
|
||||
layout.prepend(Self::layout_tree(tree));
|
||||
tree_id = store.prev_tree(room, actual_tree_id).await;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
layout
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +200,7 @@ impl<M: Msg> TreeView<M> {
|
|||
&mut self,
|
||||
store: &mut S,
|
||||
room: &str,
|
||||
cursor: &Option<Cursor<M::Id>>,
|
||||
frame: &mut Frame,
|
||||
pos: Pos,
|
||||
size: Size,
|
||||
|
|
|
|||
|
|
@ -85,5 +85,9 @@ impl<M: Msg> Tree<M> {
|
|||
#[async_trait]
|
||||
pub trait MsgStore<M: Msg> {
|
||||
async fn path(&self, room: &str, id: &M::Id) -> Path<M::Id>;
|
||||
async fn thread(&self, room: &str, root: &M::Id) -> Tree<M>;
|
||||
async fn tree(&self, room: &str, root: &M::Id) -> Tree<M>;
|
||||
async fn prev_tree(&self, room: &str, tree: &M::Id) -> Option<M::Id>;
|
||||
async fn next_tree(&self, room: &str, tree: &M::Id) -> Option<M::Id>;
|
||||
async fn first_tree(&self, room: &str) -> Option<M::Id>;
|
||||
async fn last_tree(&self, room: &str) -> Option<M::Id>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::thread::Thread;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
|
|
@ -90,6 +89,24 @@ impl DummyStore {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn trees(&self) -> Vec<usize> {
|
||||
let mut trees = HashSet::new();
|
||||
for m in self.msgs.values() {
|
||||
match m.parent() {
|
||||
Some(parent) if !self.msgs.contains_key(&parent) => {
|
||||
trees.insert(parent);
|
||||
}
|
||||
Some(_) => {}
|
||||
None => {
|
||||
trees.insert(m.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut trees: Vec<usize> = trees.into_iter().collect();
|
||||
trees.sort_unstable();
|
||||
trees
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
|
@ -105,9 +122,35 @@ impl MsgStore<DummyMsg> for DummyStore {
|
|||
Path::new(segments)
|
||||
}
|
||||
|
||||
async fn thread(&self, _room: &str, root: &usize) -> Tree<DummyMsg> {
|
||||
async fn tree(&self, _room: &str, root: &usize) -> Tree<DummyMsg> {
|
||||
let mut msgs = vec![];
|
||||
self.collect_tree(*root, &mut msgs);
|
||||
Tree::new(*root, msgs)
|
||||
}
|
||||
|
||||
async fn prev_tree(&self, _room: &str, tree: &usize) -> Option<usize> {
|
||||
let trees = self.trees();
|
||||
trees
|
||||
.iter()
|
||||
.zip(trees.iter().skip(1))
|
||||
.find(|(_, t)| *t == tree)
|
||||
.map(|(t, _)| *t)
|
||||
}
|
||||
|
||||
async fn next_tree(&self, _room: &str, tree: &usize) -> Option<usize> {
|
||||
let trees = self.trees();
|
||||
trees
|
||||
.iter()
|
||||
.zip(trees.iter().skip(1))
|
||||
.find(|(t, _)| *t == tree)
|
||||
.map(|(_, t)| *t)
|
||||
}
|
||||
|
||||
async fn first_tree(&self, _room: &str) -> Option<usize> {
|
||||
self.trees().first().cloned()
|
||||
}
|
||||
|
||||
async fn last_tree(&self, _room: &str) -> Option<usize> {
|
||||
self.trees().last().cloned()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue