From b896dd897a0ae7a07865754ba2758ca1dfd60fee Mon Sep 17 00:00:00 2001 From: Michael Sippel Date: Fri, 22 Jan 2021 14:56:56 +0100 Subject: [PATCH] add ProjectionArg helper to simplify projection views with multiple inputs --- src/grid/mod.rs | 2 +- src/index/mod.rs | 2 +- src/leveled_term_view.rs | 88 ++++++++++++++ src/main.rs | 103 +++++++++++----- src/projection.rs | 86 +++++++++++++ src/singleton/mod.rs | 17 ++- src/string_editor.rs | 254 +++++++++++++++------------------------ 7 files changed, 364 insertions(+), 188 deletions(-) create mode 100644 src/leveled_term_view.rs create mode 100644 src/projection.rs diff --git a/src/grid/mod.rs b/src/grid/mod.rs index 8330c53..38f9309 100644 --- a/src/grid/mod.rs +++ b/src/grid/mod.rs @@ -17,7 +17,7 @@ pub trait GridView = IndexView>; //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> -impl GridView { +impl dyn GridView { pub fn range(&self) -> RangeInclusive> { let area = self.area().unwrap_or(Vec::new()); diff --git a/src/index/mod.rs b/src/index/mod.rs index 96fcbd4..d8e787d 100644 --- a/src/index/mod.rs +++ b/src/index/mod.rs @@ -53,7 +53,7 @@ impl> IndexView for Option { type Item = V::Item; fn get(&self, key: &Key) -> Option { - (self.as_ref()? as &V).get(key) + self.as_ref()?.get(key) } fn area(&self) -> Option> { diff --git a/src/leveled_term_view.rs b/src/leveled_term_view.rs new file mode 100644 index 0000000..e33cba4 --- /dev/null +++ b/src/leveled_term_view.rs @@ -0,0 +1,88 @@ +use { + std::sync::{Arc, RwLock}, + cgmath::Point2, + crate::{ + core::{ViewPort, Observer, ObserverExt, ObserverBroadcast, InnerViewPort, OuterViewPort}, + index::{ImplIndexView}, + terminal::{TerminalAtom, TerminalView, TerminalStyle}, + projection::ProjectionArg + } +}; + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> + +pub struct LeveledTermView { + src: Arc>>>, + _src_obs: Arc>>, + level: usize, + cast: Arc>> +} + +impl LeveledTermView { + pub fn new( + src: OuterViewPort + ) -> (Arc>, OuterViewPort) { + let port = ViewPort::new(); + let v = Self::with_port(src, port.inner()); + (v, port.into_outer()) + } + + pub fn with_port( + src_port: OuterViewPort, + dst_port: InnerViewPort + ) -> Arc> { + let src_obs = ProjectionArg::new( + // we simply forward all messages + |s: Arc>, msg: &Point2| { + s.read().unwrap().cast.notify(msg); + } + ); + + let v = Arc::new(RwLock::new( + LeveledTermView { + src: src_obs.read().unwrap().src.clone(), + _src_obs: src_obs.clone(), + level: 0, + cast: dst_port.get_broadcast() + } + )); + + src_obs.write().unwrap().proj = Arc::downgrade(&v); + + src_port.add_observer(src_obs); + dst_port.set_view(Some(v.clone())); + + v + } + + pub fn set_level(&mut self, l: usize) { + self.level = l; + + // update complete area + if let Some(a) = self.src.area() { + self.cast.notify_each(a); + } + } +} + +impl ImplIndexView for LeveledTermView { + type Key = Point2; + type Value = TerminalAtom; + + fn get(&self, pos: &Point2) -> Option { + self.src.get(pos).map( + |a| a.add_style_front( + if self.level > 0 { + TerminalStyle::bold(true) + .add(TerminalStyle::bg_color((0, 0, 0))) + } else { + TerminalStyle::bold(false) + }) + ) + } + + fn area(&self) -> Option>> { + self.src.area() + } +} + diff --git a/src/main.rs b/src/main.rs index 66d2022..1228638 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,9 @@ pub mod grid; pub mod sequence; pub mod singleton; pub mod terminal; +pub mod projection; pub mod string_editor; +pub mod leveled_term_view; use { async_std::{task}, @@ -17,7 +19,7 @@ use { cgmath::{Vector2, Point2}, termion::event::{Event, Key}, crate::{ - core::{View, Observer, ObserverExt, ViewPort}, + core::{View, Observer, ObserverExt, ObserverBroadcast, ViewPort}, index::{ImplIndexView}, terminal::{ TerminalView, @@ -29,7 +31,8 @@ use { }, grid::{GridOffset, GridWindowIterator}, singleton::{SingletonView, SingletonBuffer}, - string_editor::{StringEditor} + string_editor::{StringEditor, insert_view::StringInsertView}, + leveled_term_view::LeveledTermView } }; @@ -56,24 +59,28 @@ async fn main() { let mut window_size = SingletonBuffer::new(Vector2::new(0, 0), window_size_port.inner()); //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> - // string editor + // string editor 1 let mut editor = StringEditor::new(); - - let edit_view_port = ViewPort::new(); - let edit_view = - string_editor::insert_view::StringEditView::new( - editor.get_cursor_port(), - editor.get_data_port().to_sequence(), - edit_view_port.inner() - ); - + let (leveled_edit_view, leveled_edit_view_port) = LeveledTermView::new(editor.insert_view()); compositor.push( - edit_view_port.outer() + leveled_edit_view_port .map_item( - |_pos, atom| atom.add_style_back( - TerminalStyle::fg_color((200,200,200)) - .add(TerminalStyle::bg_color((0,0,0))) - .add(TerminalStyle::bold(true))) + move |_pos, atom| atom.add_style_back( + 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( + 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)) ) ); @@ -84,29 +91,68 @@ async fn main() { .to_sequence() .to_index() .map_key( - |idx| Point2::new(*idx as i16, 2), - |pt| if pt.y == 2 { Some(pt.x as usize) } else { None } + |idx| Point2::new(*idx as i16, 2 + *idx as i16), + |pt| if pt.x == pt.y-2 { Some(pt.x as usize) } else { None } ).map_item( - |_key, c| TerminalAtom::new(*c, TerminalStyle::fg_color((0, 200, 0))) + |_key, c| TerminalAtom::new(*c, TerminalStyle::fg_color((80, 20, 180)).add(TerminalStyle::bg_color((40,10,90)))) ) ); + //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> + // welcome message + for c in "Welcome!".chars() { + editor.insert(c); + task::sleep(std::time::Duration::from_millis(80)).await; + } + + task::sleep(std::time::Duration::from_millis(500)).await; + + for c in "Use arrow keys to navigate.".chars() { + editor2.insert(c); + task::sleep(std::time::Duration::from_millis(80)).await; + } + + /*\ <<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> Event Loop <<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> \*/ + + let mut sel = 0; + + leveled_edit_view.write().unwrap().set_level(if sel == 0 {1} else {0}); + leveled_edit2_view.write().unwrap().set_level(if sel == 1 {1} else {0}); + loop { + let ed = match sel { + 0 => &mut editor, + 1 => &mut editor2, + _ => &mut editor2 + }; + match term.next_event().await { TerminalEvent::Resize(size) => window_size.set(size), - TerminalEvent::Input(Event::Key(Key::Left)) => editor.prev(), - TerminalEvent::Input(Event::Key(Key::Right)) => editor.next(), - TerminalEvent::Input(Event::Key(Key::Home)) => editor.goto(0), - TerminalEvent::Input(Event::Key(Key::End)) => editor.goto_end(), + TerminalEvent::Input(Event::Key(Key::Up)) => { + sel = 0; + + leveled_edit_view.write().unwrap().set_level(if sel == 0 {1} else {0}); + leveled_edit2_view.write().unwrap().set_level(if sel == 1 {1} else {0}); + }, + TerminalEvent::Input(Event::Key(Key::Down)) => { + sel = 1; + + leveled_edit_view.write().unwrap().set_level(if sel == 0 {1} else {0}); + leveled_edit2_view.write().unwrap().set_level(if sel == 1 {1} else {0}); + }, + TerminalEvent::Input(Event::Key(Key::Left)) => ed.prev(), + TerminalEvent::Input(Event::Key(Key::Right)) => ed.next(), + TerminalEvent::Input(Event::Key(Key::Home)) => ed.goto(0), + TerminalEvent::Input(Event::Key(Key::End)) => ed.goto_end(), TerminalEvent::Input(Event::Key(Key::Char('\n'))) => {}, - TerminalEvent::Input(Event::Key(Key::Char(c))) => editor.insert(c), - TerminalEvent::Input(Event::Key(Key::Delete)) => editor.delete(), - TerminalEvent::Input(Event::Key(Key::Backspace)) => { editor.prev(); editor.delete(); }, + TerminalEvent::Input(Event::Key(Key::Char(c))) => ed.insert(c), + TerminalEvent::Input(Event::Key(Key::Delete)) => ed.delete(), + TerminalEvent::Input(Event::Key(Key::Backspace)) => ed.delete_prev(), TerminalEvent::Input(Event::Key(Key::Ctrl('c'))) => break, _ => {} } @@ -155,7 +201,7 @@ impl ImplIndexView for TermLabel { fn get(&self, pos: &Point2) -> Option { if pos.y == 5 { - Some(TerminalAtom::from(self.0.chars().nth(pos.x as usize)?)) + Some(TerminalAtom::new(self.0.chars().nth(pos.x as usize)?, TerminalStyle::fg_color((255, 255, 255)))) } else { None } @@ -189,4 +235,3 @@ impl ImplIndexView for ScrambleBackground { } } - diff --git a/src/projection.rs b/src/projection.rs new file mode 100644 index 0000000..00b7346 --- /dev/null +++ b/src/projection.rs @@ -0,0 +1,86 @@ +use { + std::{ + sync::{Arc, RwLock, Weak}, + cmp::{max} + }, + crate::{ + core::{View, Observer, ObserverExt}, + singleton::{SingletonView}, + sequence::{SequenceView}, + index::{IndexView} + } +}; + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> + +/// Special Observer which can access the state of the projection on notify +/// also handles the reset() and default behaviour of unitinitalized inputs +pub struct ProjectionArg +where V: View + ?Sized, + P: Send + Sync { + pub src: Arc>>>, + pub proj: Weak>, + notify_fn: Box>, &V::Msg) + Send + Sync> +} + +impl ProjectionArg +where V: View + ?Sized, + P: Send + Sync { + pub fn new(f: impl Fn(Arc>, &V::Msg) + Send + Sync + 'static) -> Arc> { + Arc::new(RwLock::new(ProjectionArg { + src: Arc::new(RwLock::new(None)), + proj: Weak::new(), + notify_fn: Box::new(f) + })) + } +} + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> + +impl Observer> for ProjectionArg, P> +where P: Send + Sync { + fn reset(&mut self, new_src: Option>>) { + *self.src.write().unwrap() = new_src; + self.notify(&()); + } + + fn notify(&self, msg: &()) { + (self.notify_fn)(self.proj.upgrade().unwrap(), msg); + } +} + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> + +impl Observer> for ProjectionArg, P> +where P: Send + Sync { + fn reset(&mut self, new_src: Option>>) { + let old_len = self.src.len().unwrap_or(0); + *self.src.write().unwrap() = new_src; + let new_len = self.src.len().unwrap_or(0); + + self.notify_each(0 .. max(old_len, new_len)); + } + + fn notify(&self, msg: &usize) { + (self.notify_fn)(self.proj.upgrade().unwrap(), msg); + } +} + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> + +impl Observer> for ProjectionArg, P> +where P: Send + Sync { + fn reset(&mut self, new_src: Option>>) { + let old_area = self.src.area(); + *self.src.write().unwrap() = new_src; + let new_area = self.src.area(); + + if let Some(area) = old_area { self.notify_each(area); } + if let Some(area) = new_area { self.notify_each(area); } + } + + fn notify(&self, msg: &Key) { + (self.notify_fn)(self.proj.upgrade().unwrap(), msg); + } +} + diff --git a/src/singleton/mod.rs b/src/singleton/mod.rs index 812154b..48e95f9 100644 --- a/src/singleton/mod.rs +++ b/src/singleton/mod.rs @@ -20,7 +20,7 @@ pub trait SingletonView : View { //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> -impl SingletonView for RwLock { +impl SingletonView for RwLock { type Item = V::Item; fn get(&self) -> Self::Item { @@ -28,7 +28,7 @@ impl SingletonView for RwLock { } } -impl SingletonView for Arc { +impl SingletonView for Arc { type Item = V::Item; fn get(&self) -> Self::Item { @@ -36,6 +36,19 @@ impl SingletonView for Arc { } } +impl SingletonView for Option +where V::Item: Default{ + type Item = V::Item; + + fn get(&self) -> Self::Item { + if let Some(s) = self.as_ref() { + s.get() + } else { + V::Item::default() + } + } +} + //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> /* pub trait ImplSingletonView : Send + Sync { diff --git a/src/string_editor.rs b/src/string_editor.rs index d270be9..6dddd30 100644 --- a/src/string_editor.rs +++ b/src/string_editor.rs @@ -1,9 +1,10 @@ use { - std::sync::{Arc, RwLock}, + std::sync::{RwLock}, crate::{ - core::{ViewPort, OuterViewPort, InnerViewPort}, + core::{ViewPort, OuterViewPort}, singleton::{SingletonView, SingletonBuffer}, - sequence::VecBuffer + sequence::VecBuffer, + terminal::{TerminalView} } }; @@ -32,6 +33,17 @@ impl StringEditor { } } + pub fn insert_view(&self) -> OuterViewPort { + let port = ViewPort::new(); + insert_view::StringInsertView::new( + self.get_cursor_port(), + self.get_data_port().to_sequence(), + port.inner() + ); + + port.into_outer() + } + pub fn get_data_port(&self) -> OuterViewPort>> { self.data_port.outer() } @@ -66,6 +78,14 @@ impl StringEditor { self.next(); } + pub fn delete_prev(&mut self) { + let cur = self.cursor.get(); + if cur <= self.data.len() && cur > 0 { + self.data.remove(cur-1); + } + self.prev(); + } + pub fn delete(&mut self) { let cur = self.cursor.get(); if cur < self.data.len() { @@ -77,166 +97,49 @@ impl StringEditor { //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>> pub mod insert_view { - use { - std::sync::{Arc, RwLock, Weak}, - cgmath::Point2, - crate::{ - core::{Observer, ObserverExt, ObserverBroadcast, OuterViewPort, InnerViewPort}, - singleton::SingletonView, - sequence::SequenceView, - index::ImplIndexView, - grid::GridWindowIterator, - terminal::{TerminalAtom, TerminalStyle, TerminalView} - } + use cgmath::Point2; + use std::sync::{Arc, RwLock}; + use std::cmp::{min, max}; + use crate::{ + core::{View, Observer, ObserverExt, ObserverBroadcast, OuterViewPort, InnerViewPort}, + terminal::{TerminalAtom, TerminalStyle, TerminalView}, + grid::{GridWindowIterator}, + singleton::{SingletonView}, + sequence::{SequenceView}, + index::{IndexView}, + projection::ProjectionArg, }; - struct CursorObserver { - cursor: Option>>, - edit: Weak> - } + pub struct StringInsertView { + _cursor_obs: Arc, Self>>>, + _data_obs: Arc, Self>>>, - impl Observer> for CursorObserver { - fn reset(&mut self, new_cursor: Option>>) { - self.cursor = new_cursor; - - if let Some(cursor) = self.cursor.as_ref() { - self.edit - .upgrade().unwrap() - .write().unwrap() - .update_cursor( cursor.get() ); - } - } - - fn notify(&self, _msg: &()) { - if let Some(cursor) = self.cursor.as_ref() { - self.edit - .upgrade().unwrap() - .write().unwrap() - .update_cursor( cursor.get() ); - } - } - } - - struct DataObserver { - data: Option>>, - edit: Weak> - } - - impl Observer> for DataObserver { - fn reset(&mut self, new_data: Option>>) { - let old_len = self.data.len().unwrap_or(0); - self.data = new_data; - let new_len = self.data.len().unwrap_or(0); - - self.edit - .upgrade().unwrap() - .write().unwrap() - .reset_data( std::cmp::max(old_len, new_len) ); - } - - fn notify(&self, pos: &usize) { - self.edit - .upgrade().unwrap() - .write().unwrap() - .update_data( *pos ); - } - } - - pub struct StringEditView { - data_obs: Option>>, - cursor_obs: Option>>, + cursor: Arc>, + data: Arc>, cur_pos: usize, cast: Arc>> } - - impl StringEditView { - pub fn new( - cursor_port: OuterViewPort>, - data_port: OuterViewPort>, - out_port: InnerViewPort - ) -> Arc> { - let edit_view = Arc::new(RwLock::new( - StringEditView { - data_obs: None, - cursor_obs: None, - cur_pos: 0, - cast: out_port.get_broadcast() - } - )); - let data_obs = Arc::new(RwLock::new( - DataObserver { - data: None, - edit: Arc::downgrade(&edit_view) - } - )); - edit_view.write().unwrap().data_obs = Some(data_obs.clone()); - data_port.add_observer(data_obs); - - let cursor_obs = Arc::new(RwLock::new( - CursorObserver { - cursor: None, - edit: Arc::downgrade(&edit_view) - } - )); - edit_view.write().unwrap().cursor_obs = Some(cursor_obs.clone()); - cursor_port.add_observer(cursor_obs); - - out_port.set_view(Some(edit_view.clone())); - edit_view - } - - fn reset_data(&mut self, max_len: usize) { - self.cast.notify_each(GridWindowIterator::from( - Point2::new(0, 0) .. Point2::new(max_len as i16 + 1, 1) - )); - } - - fn update_data(&mut self, pos: usize) { - self.cast.notify( - &Point2::new( - if pos < self.cur_pos { - pos - } else { - pos + 1 - } as i16, - 0 - ) - ); - } - - fn update_cursor(&mut self, new_pos: usize) { - let old_pos = self.cur_pos; - self.cur_pos = new_pos; - - self.cast.notify_each(GridWindowIterator::from( - Point2::new(std::cmp::min(old_pos,new_pos) as i16, 0) .. Point2::new(std::cmp::max(old_pos,new_pos) as i16 + 1, 1) - )); - } + impl View for StringInsertView { + type Msg = Point2; } - - impl ImplIndexView for StringEditView { - type Key = Point2; - type Value = TerminalAtom; + + impl IndexView> for StringInsertView { + type Item = TerminalAtom; fn get(&self, pos: &Point2) -> Option { if pos.y == 0 && pos.x >= 0 { let i = pos.x as usize; - let data = - self.data_obs.as_ref().unwrap() - .read().unwrap() - .data.clone() - .unwrap(); - let len = data.len().unwrap(); + let len = self.data.len().unwrap_or(0); if i < len+1 { return Some( if i < self.cur_pos && i < len { - TerminalAtom::from(data.get(&i).unwrap()) + TerminalAtom::from(self.data.get(&i).unwrap()) } else if i == self.cur_pos { TerminalAtom::new('|', TerminalStyle::fg_color((200, 0, 0))) } else { - TerminalAtom::from(data.get(&(i-1)).unwrap()) + TerminalAtom::from(self.data.get(&(i-1)).unwrap()) } ); } @@ -246,18 +149,59 @@ pub mod insert_view { } fn area(&self) -> Option>> { - let data = - self.data_obs.as_ref().unwrap() - .read().unwrap() - .data.clone() - .unwrap(); - let len = data.len()?; + Some(GridWindowIterator::from( + Point2::new(0, 0) .. Point2::new(self.data.len()? as i16 + 1, 1) + ).collect()) + } + } - Some( - GridWindowIterator::from( - Point2::new(0, 0) .. Point2::new(len as i16 + 1, 1) - ).collect() - ) + impl StringInsertView { + pub fn new( + cursor_port: OuterViewPort>, + data_port: OuterViewPort>, + out_port: InnerViewPort + ) -> Arc> { + let cursor_obs = ProjectionArg::new( + |s: Arc>, _msg| { + let old_pos = s.read().unwrap().cur_pos; + let new_pos = s.read().unwrap().cursor.get(); + s.write().unwrap().cur_pos = new_pos; + s.read().unwrap().cast.notify_each(GridWindowIterator::from(Point2::new(min(old_pos, new_pos) as i16,0) ..= Point2::new(max(old_pos, new_pos) as i16, 0))) + }); + + let data_obs = ProjectionArg::new( + |s: Arc>, idx| { + s.read().unwrap().cast.notify(&Point2::new( + if *idx < s.read().unwrap().cur_pos { + *idx as i16 + } else { + *idx as i16 + 1 + }, + 0 + )); + }); + + let proj = Arc::new(RwLock::new( + StringInsertView { + _cursor_obs: cursor_obs.clone(), + _data_obs: data_obs.clone(), + + cursor: cursor_obs.read().unwrap().src.clone(), + data: data_obs.read().unwrap().src.clone(), + cur_pos: 0, + cast: out_port.get_broadcast() + } + )); + + cursor_obs.write().unwrap().proj = Arc::downgrade(&proj); + data_obs.write().unwrap().proj = Arc::downgrade(&proj); + + cursor_port.add_observer(cursor_obs); + data_port.add_observer(data_obs); + + out_port.set_view(Some(proj.clone())); + + proj } } }