diff --git a/nested/src/cell_layout.rs b/nested/src/cell_layout.rs deleted file mode 100644 index ea4de0b..0000000 --- a/nested/src/cell_layout.rs +++ /dev/null @@ -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, - _arg: Arc>> -} - -pub struct CellLayout { - cells: HashMap, Cell>, - - col_widths: Vec, - row_heights: Vec, - - cast: Arc>>, - - send: ChannelSender, Point2)>> -} - -impl ImplIndexView for CellLayout { - type Key = Point2; - type Value = TerminalAtom; - - fn get(&self, pos: &Point2) -> Option { - 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>> { - 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) -> Arc> { - 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>, cell_pos: Point2, port: OuterViewPort) { - let sender = layout.read().unwrap().send.clone(); - let arg = ProjectionArg::new( - move |s: Arc>, pos: &Point2| { - 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, pos: &Point2) { - 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) -> Vector2 { - Vector2::new( - self.col_widths.iter().take(cell_pos.x as usize).sum::() as i16, - self.row_heights.iter().take(cell_pos.y as usize).sum::() as i16 - ) - } - - fn get_cell_containing(&self, glob_pos: &Point2) -> Point2 { - 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 - ) - } -} - diff --git a/nested/src/grid/flatten.rs b/nested/src/grid/flatten.rs new file mode 100644 index 0000000..6ba62f4 --- /dev/null +++ b/nested/src/grid/flatten.rs @@ -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 OuterViewPort>>> +where Item: 'static{ + pub fn flatten(&self) -> OuterViewPort> { + 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 +where Item: 'static +{ + offset: Vector2, + limit: Point2, + view: Arc> +} + +pub struct Flatten +where Item: 'static +{ + limit: Point2, + top: Arc>>>, + chunks: HashMap, Chunk>, + cast: Arc>>>, + proj_helper: ProjectionHelper +} + +impl View for Flatten +where Item: 'static +{ + type Msg = Point2; +} + +impl IndexView> for Flatten +where Item: 'static +{ + type Item = Item; + + fn get(&self, idx: &Point2) -> Option { + 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>> { + 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 Flatten +where Item: 'static +{ + pub fn new( + top_port: OuterViewPort>>>, + out_port: InnerViewPort> + ) -> Arc> { + 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) { + 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> { + 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) -> Option> { + 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 + } +} +