grid flatten
This commit is contained in:
parent
44cba54320
commit
30a3df180b
2 changed files with 209 additions and 239 deletions
|
@ -1,239 +0,0 @@
|
||||||
use {
|
|
||||||
async_std::stream::StreamExt,
|
|
||||||
std::{
|
|
||||||
sync::Arc,
|
|
||||||
collections::HashMap,
|
|
||||||
cmp::{min, max}
|
|
||||||
},
|
|
||||||
cgmath::{Point2, Vector2},
|
|
||||||
std::sync::RwLock,
|
|
||||||
crate::{
|
|
||||||
core::{InnerViewPort, OuterViewPort, Observer, ObserverExt, ObserverBroadcast, ChannelReceiver, ChannelSender},
|
|
||||||
terminal::{TerminalView, TerminalAtom},
|
|
||||||
index::{ImplIndexView},
|
|
||||||
grid::GridWindowIterator,
|
|
||||||
projection::ProjectionArg
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
pub struct Cell {
|
|
||||||
view: Arc<dyn TerminalView>,
|
|
||||||
_arg: Arc<RwLock<ProjectionArg<dyn TerminalView, CellLayout>>>
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CellLayout {
|
|
||||||
cells: HashMap<Point2<i16>, Cell>,
|
|
||||||
|
|
||||||
col_widths: Vec<usize>,
|
|
||||||
row_heights: Vec<usize>,
|
|
||||||
|
|
||||||
cast: Arc<RwLock<ObserverBroadcast<dyn TerminalView>>>,
|
|
||||||
|
|
||||||
send: ChannelSender<Vec<(Point2<i16>, Point2<i16>)>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImplIndexView for CellLayout {
|
|
||||||
type Key = Point2<i16>;
|
|
||||||
type Value = TerminalAtom;
|
|
||||||
|
|
||||||
fn get(&self, pos: &Point2<i16>) -> Option<TerminalAtom> {
|
|
||||||
let cell_pos = self.get_cell_containing(pos);
|
|
||||||
let cell_off = self.get_cell_offset(&cell_pos);
|
|
||||||
|
|
||||||
self.cells.get(&cell_pos)?.view.get(&(pos - cell_off))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn area(&self) -> Option<Vec<Point2<i16>>> {
|
|
||||||
Some(
|
|
||||||
self.cells.iter()
|
|
||||||
.flat_map(
|
|
||||||
|(cell_pos, cell)| {
|
|
||||||
let off = self.get_cell_offset(cell_pos);
|
|
||||||
|
|
||||||
cell.view.area()
|
|
||||||
.unwrap_or(Vec::new())
|
|
||||||
.into_iter()
|
|
||||||
.map(move |p| p + off)
|
|
||||||
}
|
|
||||||
).collect()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CellLayout {
|
|
||||||
pub fn with_port(port: InnerViewPort<dyn TerminalView>) -> Arc<RwLock<Self>> {
|
|
||||||
let (send, mut recv) = crate::core::channel::channel();
|
|
||||||
let v = Arc::new(RwLock::new(CellLayout {
|
|
||||||
cells: HashMap::new(),
|
|
||||||
col_widths: Vec::new(),
|
|
||||||
row_heights: Vec::new(),
|
|
||||||
cast: port.get_broadcast(),
|
|
||||||
send
|
|
||||||
}));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* its is a bit ugly to spawn a task here, but we need the stream to decouple
|
|
||||||
* in order to avoid deadlocks
|
|
||||||
*/
|
|
||||||
async_std::task::spawn({
|
|
||||||
let l = v.clone();
|
|
||||||
async move {
|
|
||||||
while let Some((cell_idx, pos)) = recv.next().await {
|
|
||||||
l.write().unwrap().update_cell(&cell_idx, &pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
port.set_view(Some(v.clone()));
|
|
||||||
v
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cell(layout: &Arc<RwLock<Self>>, cell_pos: Point2<i16>, port: OuterViewPort<dyn TerminalView>) {
|
|
||||||
let sender = layout.read().unwrap().send.clone();
|
|
||||||
let arg = ProjectionArg::new(
|
|
||||||
move |s: Arc<RwLock<Self>>, pos: &Point2<i16>| {
|
|
||||||
sender.send((cell_pos, *pos));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
layout.write().unwrap().cells.insert(
|
|
||||||
cell_pos,
|
|
||||||
Cell {
|
|
||||||
view: arg.read().unwrap().src.clone(),
|
|
||||||
_arg: arg.clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
arg.write().unwrap().proj = Arc::downgrade(&layout);
|
|
||||||
port.add_observer(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_col_width(&mut self, col_idx: i16) -> bool {
|
|
||||||
let mut max_width = 0;
|
|
||||||
|
|
||||||
for row_idx in 0 .. self.row_heights.len() as i16 {
|
|
||||||
if let Some(cell) = self.cells.get(&Point2::new(col_idx, row_idx)) {
|
|
||||||
if let Some(area) = cell.view.area() {
|
|
||||||
max_width = max(
|
|
||||||
max_width,
|
|
||||||
area.iter()
|
|
||||||
.map(|pt| pt.x as usize + 1)
|
|
||||||
.max()
|
|
||||||
.unwrap_or(0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let changed = (self.col_widths[col_idx as usize] != max_width);
|
|
||||||
self.col_widths[col_idx as usize] = max_width;
|
|
||||||
changed
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_row_height(&mut self, row_idx: i16) -> bool {
|
|
||||||
let mut max_height = 0;
|
|
||||||
|
|
||||||
for col_idx in 0 .. self.col_widths.len() as i16 {
|
|
||||||
if let Some(cell) = self.cells.get(&Point2::new(col_idx, row_idx)) {
|
|
||||||
if let Some(area) = cell.view.area() {
|
|
||||||
max_height = max(
|
|
||||||
max_height,
|
|
||||||
area.iter()
|
|
||||||
.map(|pt| pt.y as usize + 1)
|
|
||||||
.max()
|
|
||||||
.unwrap_or(0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let changed = (self.row_heights[row_idx as usize] != max_height);
|
|
||||||
self.row_heights[row_idx as usize] = max_height;
|
|
||||||
changed
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_cell(&mut self, cell_pos: &Point2<i16>, pos: &Point2<i16>) {
|
|
||||||
for _ in self.col_widths.len() as i16 ..= cell_pos.x { self.col_widths.push(0); }
|
|
||||||
for _ in self.row_heights.len() as i16 ..= cell_pos.y { self.row_heights.push(0); }
|
|
||||||
|
|
||||||
let cell_off = self.get_cell_offset(cell_pos);
|
|
||||||
self.cast.notify(&(pos + cell_off));
|
|
||||||
|
|
||||||
let old_n = self.get_cell_offset(&(cell_pos + Vector2::new(1, 1)));
|
|
||||||
let old_width = self.get_width();
|
|
||||||
let old_height = self.get_height();
|
|
||||||
|
|
||||||
// does this really have to be recalculated every time ??
|
|
||||||
let width_changed = self.update_col_width(cell_pos.x);
|
|
||||||
let height_changed = self.update_row_height(cell_pos.y);
|
|
||||||
|
|
||||||
let extent = Point2::new(
|
|
||||||
max(self.get_width(), old_width) as i16,
|
|
||||||
max(self.get_height(), old_height) as i16
|
|
||||||
);
|
|
||||||
let new_n = self.get_cell_offset(&(cell_pos + Vector2::new(1, 1)));
|
|
||||||
|
|
||||||
/* if a cell updates its size, the complete rectangle to the right is refreshed
|
|
||||||
* todo: optimize to use area() of cell views
|
|
||||||
*/
|
|
||||||
if width_changed {
|
|
||||||
self.cast.notify_each(GridWindowIterator::from(
|
|
||||||
Point2::new(
|
|
||||||
min(old_n.x, new_n.x),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
..
|
|
||||||
extent
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if height_changed {
|
|
||||||
self.cast.notify_each(GridWindowIterator::from(
|
|
||||||
Point2::new(
|
|
||||||
0,
|
|
||||||
min(old_n.y, new_n.y)
|
|
||||||
)
|
|
||||||
..
|
|
||||||
extent
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_width(&self) -> usize {
|
|
||||||
self.col_widths.iter().sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_height(&self) -> usize {
|
|
||||||
self.row_heights.iter().sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cell_offset(&self, cell_pos: &Point2<i16>) -> Vector2<i16> {
|
|
||||||
Vector2::new(
|
|
||||||
self.col_widths.iter().take(cell_pos.x as usize).sum::<usize>() as i16,
|
|
||||||
self.row_heights.iter().take(cell_pos.y as usize).sum::<usize>() as i16
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cell_containing(&self, glob_pos: &Point2<i16>) -> Point2<i16> {
|
|
||||||
Point2::new(
|
|
||||||
self.col_widths.iter()
|
|
||||||
.fold(
|
|
||||||
(0, 0),
|
|
||||||
|(cell_idx, x), width|
|
|
||||||
(
|
|
||||||
cell_idx + if (x + *width as i16) <= glob_pos.x { 1 } else { 0 },
|
|
||||||
x + *width as i16
|
|
||||||
)).0,
|
|
||||||
|
|
||||||
self.row_heights.iter()
|
|
||||||
.fold(
|
|
||||||
(0, 0),
|
|
||||||
|(cell_idx, y), height|
|
|
||||||
(
|
|
||||||
cell_idx + if (y + *height as i16) <= glob_pos.y { 1 } else { 0 },
|
|
||||||
y + *height as i16
|
|
||||||
)).0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
209
nested/src/grid/flatten.rs
Normal file
209
nested/src/grid/flatten.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
use {
|
||||||
|
async_std::stream::StreamExt,
|
||||||
|
std::{
|
||||||
|
sync::{Arc},
|
||||||
|
collections::{HashMap, HashSet}
|
||||||
|
},
|
||||||
|
std::sync::RwLock,
|
||||||
|
cgmath::{Point2, Vector2},
|
||||||
|
crate::{
|
||||||
|
core::{
|
||||||
|
View, Observer, ObserverBroadcast, ObserverExt,
|
||||||
|
ViewPort, InnerViewPort, OuterViewPort,
|
||||||
|
channel::{ChannelSender, ChannelReceiver},
|
||||||
|
port::UpdateTask
|
||||||
|
},
|
||||||
|
grid::{GridView, GridWindowIterator},
|
||||||
|
index::IndexView,
|
||||||
|
projection::ProjectionHelper
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<Item> OuterViewPort<dyn GridView<Item = OuterViewPort<dyn GridView<Item = Item>>>>
|
||||||
|
where Item: 'static{
|
||||||
|
pub fn flatten(&self) -> OuterViewPort<dyn GridView<Item = Item>> {
|
||||||
|
let port = ViewPort::new();
|
||||||
|
port.add_update_hook(Arc::new(self.0.clone()));
|
||||||
|
Flatten::new(self.clone(), port.inner());
|
||||||
|
port.into_outer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Chunk<Item>
|
||||||
|
where Item: 'static
|
||||||
|
{
|
||||||
|
offset: Vector2<i16>,
|
||||||
|
limit: Point2<i16>,
|
||||||
|
view: Arc<dyn GridView<Item = Item>>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Flatten<Item>
|
||||||
|
where Item: 'static
|
||||||
|
{
|
||||||
|
limit: Point2<i16>,
|
||||||
|
top: Arc<dyn GridView<Item = OuterViewPort<dyn GridView<Item = Item>>>>,
|
||||||
|
chunks: HashMap<Point2<i16>, Chunk<Item>>,
|
||||||
|
cast: Arc<RwLock<ObserverBroadcast<dyn GridView<Item = Item>>>>,
|
||||||
|
proj_helper: ProjectionHelper<Self>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Item> View for Flatten<Item>
|
||||||
|
where Item: 'static
|
||||||
|
{
|
||||||
|
type Msg = Point2<i16>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Item> IndexView<Point2<i16>> for Flatten<Item>
|
||||||
|
where Item: 'static
|
||||||
|
{
|
||||||
|
type Item = Item;
|
||||||
|
|
||||||
|
fn get(&self, idx: &Point2<i16>) -> Option<Self::Item> {
|
||||||
|
let chunk_idx = self.get_chunk_idx(*idx)?;
|
||||||
|
let chunk = self.chunks.get(&chunk_idx)?;
|
||||||
|
chunk.view.get(&(*idx - chunk.offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn area(&self) -> Option<Vec<Point2<i16>>> {
|
||||||
|
Some(GridWindowIterator::from(Point2::new(0, 0) .. self.limit).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: remove unused projection args (bot-views) if they get replaced by a new viewport */
|
||||||
|
impl<Item> Flatten<Item>
|
||||||
|
where Item: 'static
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
top_port: OuterViewPort<dyn GridView<Item = OuterViewPort<dyn GridView<Item = Item>>>>,
|
||||||
|
out_port: InnerViewPort<dyn GridView<Item = Item>>
|
||||||
|
) -> Arc<RwLock<Self>> {
|
||||||
|
let mut proj_helper = ProjectionHelper::new(out_port.0.update_hooks.clone());
|
||||||
|
|
||||||
|
let flat = Arc::new(RwLock::new(
|
||||||
|
Flatten {
|
||||||
|
limit: Point2::new(0, 0),
|
||||||
|
top: proj_helper.new_index_arg(
|
||||||
|
top_port,
|
||||||
|
|s: &mut Self, chunk_idx| {
|
||||||
|
s.update_chunk(*chunk_idx);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
chunks: HashMap::new(),
|
||||||
|
cast: out_port.get_broadcast(),
|
||||||
|
proj_helper
|
||||||
|
}));
|
||||||
|
|
||||||
|
flat.write().unwrap().proj_helper.set_proj(&flat);
|
||||||
|
out_port.set_view(Some(flat.clone()));
|
||||||
|
flat
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the top-sequence has changed the item at chunk_idx,
|
||||||
|
/// create a new observer for the contained sub sequence
|
||||||
|
fn update_chunk(&mut self, chunk_idx: Point2<i16>) {
|
||||||
|
if let Some(chunk_port) = self.top.get(&chunk_idx) {
|
||||||
|
self.chunks.insert(
|
||||||
|
chunk_idx,
|
||||||
|
Chunk {
|
||||||
|
offset: Vector2::new(0, 0),
|
||||||
|
limit: Point2::new(0, 0),
|
||||||
|
view: self.proj_helper.new_index_arg(
|
||||||
|
chunk_port.clone(),
|
||||||
|
move |s: &mut Self, idx| {
|
||||||
|
if let Some(chunk) = s.chunks.get(&chunk_idx) {
|
||||||
|
let chunk_offset = chunk.offset;
|
||||||
|
let chunk_limit = chunk.view.range().end;
|
||||||
|
|
||||||
|
let mut dirty_idx = Vec::new();
|
||||||
|
if chunk.limit != chunk_limit {
|
||||||
|
dirty_idx = s.update_all_offsets();
|
||||||
|
}
|
||||||
|
|
||||||
|
s.cast.notify(&(idx + chunk_offset));
|
||||||
|
s.cast.notify_each(dirty_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
chunk_port.0.update();
|
||||||
|
|
||||||
|
let dirty_idx = self.update_all_offsets();
|
||||||
|
self.cast.notify_each(dirty_idx);
|
||||||
|
} else {
|
||||||
|
// todo:
|
||||||
|
//self.proj_helper.remove_arg();
|
||||||
|
|
||||||
|
self.chunks.remove(&chunk_idx);
|
||||||
|
|
||||||
|
let dirty_idx = self.update_all_offsets();
|
||||||
|
self.cast.notify_each(dirty_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// recalculate all chunk offsets
|
||||||
|
/// and update size of flattened grid
|
||||||
|
fn update_all_offsets(&mut self) -> Vec<Point2<i16>> {
|
||||||
|
let mut dirty_idx = Vec::new();
|
||||||
|
|
||||||
|
let top_range = self.top.range();
|
||||||
|
let mut col_widths = vec![0 as i16; (top_range.end.x) as usize];
|
||||||
|
let mut row_heights = vec![0 as i16; (top_range.end.y) as usize];
|
||||||
|
|
||||||
|
for chunk_idx in GridWindowIterator::from(self.top.range()) {
|
||||||
|
if let Some(chunk) = self.chunks.get_mut(&chunk_idx) {
|
||||||
|
let old_offset = chunk.offset;
|
||||||
|
let old_limit = chunk.limit;
|
||||||
|
|
||||||
|
chunk.offset = Vector2::new(
|
||||||
|
(0 .. chunk_idx.x as usize).map(|x| col_widths[x]).sum(),
|
||||||
|
(0 .. chunk_idx.y as usize).map(|y| row_heights[y]).sum()
|
||||||
|
);
|
||||||
|
chunk.limit = chunk.view.range().end;
|
||||||
|
|
||||||
|
col_widths[chunk_idx.x as usize] = std::cmp::max(col_widths[chunk_idx.x as usize], chunk.limit.x);
|
||||||
|
row_heights[chunk_idx.y as usize] = std::cmp::max(row_heights[chunk_idx.y as usize], chunk.limit.y);
|
||||||
|
|
||||||
|
if old_offset != chunk.offset {
|
||||||
|
dirty_idx.extend(
|
||||||
|
GridWindowIterator::from(
|
||||||
|
Point2::new(std::cmp::min(old_offset.x, chunk.offset.x),
|
||||||
|
std::cmp::min(old_offset.y, chunk.offset.y))
|
||||||
|
.. Point2::new(std::cmp::max(old_offset.x, chunk.offset.x) + std::cmp::max(old_limit.x, chunk.limit.x),
|
||||||
|
std::cmp::max(old_offset.y, chunk.offset.y) + std::cmp::max(old_limit.y, chunk.limit.y)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_limit = self.limit;
|
||||||
|
self.limit = Point2::new(
|
||||||
|
(0 .. top_range.end.x as usize).map(|x| col_widths[x]).sum(),
|
||||||
|
(0 .. top_range.end.y as usize).map(|y| row_heights[y]).sum()
|
||||||
|
);
|
||||||
|
|
||||||
|
dirty_idx
|
||||||
|
}
|
||||||
|
|
||||||
|
/// given an index in the flattened sequence,
|
||||||
|
/// which sub-sequence does it belong to?
|
||||||
|
fn get_chunk_idx(&self, glob_pos: Point2<i16>) -> Option<Point2<i16>> {
|
||||||
|
let mut offset = Point2::new(0, 0);
|
||||||
|
|
||||||
|
for chunk_idx in GridWindowIterator::from(self.top.range()) {
|
||||||
|
if let Some(chunk) = self.chunks.get(&chunk_idx) {
|
||||||
|
let chunk_range = chunk.view.range();
|
||||||
|
|
||||||
|
offset += Vector2::new(chunk_range.end.x, chunk_range.end.y);
|
||||||
|
|
||||||
|
if glob_pos.x < offset.x && glob_pos.y < offset.y {
|
||||||
|
return Some(chunk_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue