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) {
|
pub fn render(&mut self, frame: &mut Frame, pos: Pos, size: Size) {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Tree => self
|
Mode::Tree => {
|
||||||
.tree
|
self.tree
|
||||||
.render(&mut self.store, &self.room, frame, pos, size),
|
.render(&mut self.store, &self.room, &self.cursor, frame, pos, size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crossterm::event::KeyEvent;
|
||||||
use crossterm::style::ContentStyle;
|
use crossterm::style::ContentStyle;
|
||||||
use toss::frame::{Frame, Pos, Size};
|
use toss::frame::{Frame, Pos, Size};
|
||||||
|
|
||||||
use crate::store::{Msg, MsgStore};
|
use crate::store::{Msg, MsgStore, Tree};
|
||||||
|
|
||||||
use super::Cursor;
|
use super::Cursor;
|
||||||
|
|
||||||
|
|
@ -30,61 +30,97 @@ struct MsgBlock {
|
||||||
content: Vec<String>,
|
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> {
|
impl<I: PartialEq> Layout<I> {
|
||||||
pub fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self(VecDeque::new())
|
Self::new_below(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mark_cursor(&mut self, id: &I) {
|
/// Create a new [`Layout`] such that prepending a single line will result
|
||||||
for block in &mut self.0 {
|
/// in `top_line = bottom_line = line`.
|
||||||
|
fn new_below(line: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
blocks: VecDeque::new(),
|
||||||
|
top_line: line + 1,
|
||||||
|
bottom_line: line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
if block.id.as_ref() == Some(id) {
|
||||||
block.cursor = true;
|
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, line: i32) {
|
fn calculate_offsets_with_cursor(&mut self, cursor: &Cursor<I>, height: i32) {
|
||||||
let cursor_index = self
|
let cursor_index = self.mark_cursor(&cursor.id);
|
||||||
.0
|
let cursor_line = ((height - 1) as f32 * cursor.proportion).round() as i32;
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, b)| b.cursor)
|
|
||||||
.expect("layout must contain cursor block")
|
|
||||||
.0;
|
|
||||||
|
|
||||||
// Propagate lines from cursor to both ends
|
// 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() {
|
for i in (0..cursor_index).rev() {
|
||||||
// let succ_line = self.0[i + 1].line;
|
// let succ_line = self.0[i + 1].line;
|
||||||
// let curr = &mut self.0[i];
|
// let curr = &mut self.0[i];
|
||||||
// curr.line = succ_line - curr.height;
|
// 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];
|
// let pred = &self.0[i - 1];
|
||||||
// self.0[i].line = pred.line + pred.height;
|
// 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) {
|
fn append(&mut self, mut layout: Self) {
|
||||||
if let Some(back) = self.0.back_mut() {
|
while let Some(mut block) = layout.blocks.pop_front() {
|
||||||
back.line = height - back.height;
|
block.line = self.bottom_line + 1;
|
||||||
}
|
self.bottom_line += block.height;
|
||||||
for i in (0..self.0.len() - 1).rev() {
|
self.blocks.push_back(block);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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>>(
|
async fn layout<S: MsgStore<M>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
room: &str,
|
room: &str,
|
||||||
store: S,
|
store: S,
|
||||||
cursor: &mut Option<Cursor<M::Id>>,
|
cursor: &Option<Cursor<M::Id>>,
|
||||||
|
size: Size,
|
||||||
) -> Layout<M::Id> {
|
) -> Layout<M::Id> {
|
||||||
|
let height: i32 = size.height.into();
|
||||||
if let Some(cursor) = cursor {
|
if let Some(cursor) = cursor {
|
||||||
// TODO Ensure focus lies on cursor path, otherwise unfocus
|
// TODO Ensure focus lies on cursor path, otherwise unfocus
|
||||||
// TODO Unfold all messages on path to cursor
|
// 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;
|
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 Expand layout upwards and downwards if there is no focus
|
||||||
todo!()
|
todo!()
|
||||||
} else {
|
} else {
|
||||||
// TODO Ensure there is no focus
|
// TODO Ensure there is no focus
|
||||||
// TODO Produce layout of last tree (with correct offsets)
|
|
||||||
// TODO Expand layout upwards
|
// Start layout at the bottom of the screen
|
||||||
todo!()
|
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,
|
&mut self,
|
||||||
store: &mut S,
|
store: &mut S,
|
||||||
room: &str,
|
room: &str,
|
||||||
|
cursor: &Option<Cursor<M::Id>>,
|
||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
pos: Pos,
|
pos: Pos,
|
||||||
size: Size,
|
size: Size,
|
||||||
|
|
|
||||||
|
|
@ -85,5 +85,9 @@ impl<M: Msg> Tree<M> {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait MsgStore<M: Msg> {
|
pub trait MsgStore<M: Msg> {
|
||||||
async fn path(&self, room: &str, id: &M::Id) -> Path<M::Id>;
|
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::collections::{HashMap, HashSet};
|
||||||
use std::thread::Thread;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
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]
|
#[async_trait]
|
||||||
|
|
@ -105,9 +122,35 @@ impl MsgStore<DummyMsg> for DummyStore {
|
||||||
Path::new(segments)
|
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![];
|
let mut msgs = vec![];
|
||||||
self.collect_tree(*root, &mut msgs);
|
self.collect_tree(*root, &mut msgs);
|
||||||
Tree::new(*root, 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