From 687936b098c67a5485cbfb968e759957d834bf04 Mon Sep 17 00:00:00 2001 From: Michael Sippel Date: Fri, 22 Jan 2021 20:55:42 +0100 Subject: [PATCH] first CellLayout not very efficient atm, but functional --- src/cell_layout.rs | 238 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 34 ++++--- 2 files changed, 261 insertions(+), 11 deletions(-) create mode 100644 src/cell_layout.rs diff --git a/src/cell_layout.rs b/src/cell_layout.rs new file mode 100644 index 0000000..3cdd038 --- /dev/null +++ b/src/cell_layout.rs @@ -0,0 +1,238 @@ +use { + async_std::stream::StreamExt, + std::{ + sync::{Arc, RwLock}, + collections::HashMap, + cmp::{min, max} + }, + cgmath::{Point2, Vector2}, + 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/src/main.rs b/src/main.rs index 1228638..700a3b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ pub mod terminal; pub mod projection; pub mod string_editor; pub mod leveled_term_view; +pub mod cell_layout; use { async_std::{task}, @@ -58,35 +59,45 @@ async fn main() { let window_size_port = ViewPort::new(); let mut window_size = SingletonBuffer::new(Vector2::new(0, 0), window_size_port.inner()); + //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> + // cells + + let cells_port = ViewPort::new(); + let cells = cell_layout::CellLayout::with_port(cells_port.inner()); + + compositor.push(cells_port.outer()); + //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> // string editor 1 let mut editor = StringEditor::new(); let (leveled_edit_view, leveled_edit_view_port) = LeveledTermView::new(editor.insert_view()); - compositor.push( + + cell_layout::CellLayout::set_cell( + &cells, + Point2::new(0, 0), leveled_edit_view_port .map_item( move |_pos, atom| atom.add_style_back( - TerminalStyle::fg_color((200,200,200)))) - ); + TerminalStyle::fg_color((200,200,200))))); //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> // string editor 2 let mut editor2 = StringEditor::new(); let (leveled_edit2_view, leveled_edit2_view_port) = LeveledTermView::new(editor2.insert_view()); - compositor.push( + + cell_layout::CellLayout::set_cell( + &cells, + Point2::new(1, 1), leveled_edit2_view_port .map_item( move |_pos, atom| atom.add_style_back( - TerminalStyle::fg_color((200,200,200)))) - .map_key( - |p| p + Vector2::new(0, 1), - |p| Some(p - Vector2::new(0, 1)) - ) - ); + TerminalStyle::fg_color((200,200,200))))); //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> // another view of the string, without editor - compositor.push( + cell_layout::CellLayout::set_cell( + &cells, + Point2::new(1, 0), editor.get_data_port() .to_sequence() .to_index() @@ -100,6 +111,7 @@ async fn main() { //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> // welcome message + for c in "Welcome!".chars() { editor.insert(c); task::sleep(std::time::Duration::from_millis(80)).await;