add ProjectionArg helper to simplify projection views with multiple inputs

This commit is contained in:
Michael Sippel 2021-01-22 14:56:56 +01:00
parent 3514e41432
commit b896dd897a
Signed by: senvas
GPG key ID: F96CF119C34B64A6
7 changed files with 364 additions and 188 deletions

View file

@ -17,7 +17,7 @@ pub trait GridView = IndexView<Point2<i16>>;
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
impl<Item> GridView<Item = Item> {
impl<Item> dyn GridView<Item = Item> {
pub fn range(&self) -> RangeInclusive<Point2<i16>> {
let area = self.area().unwrap_or(Vec::new());

View file

@ -53,7 +53,7 @@ impl<Key, V: IndexView<Key>> IndexView<Key> for Option<V> {
type Item = V::Item;
fn get(&self, key: &Key) -> Option<Self::Item> {
(self.as_ref()? as &V).get(key)
self.as_ref()?.get(key)
}
fn area(&self) -> Option<Vec<Key>> {

88
src/leveled_term_view.rs Normal file
View file

@ -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<RwLock<Option<Arc<dyn TerminalView>>>>,
_src_obs: Arc<RwLock<ProjectionArg<dyn TerminalView, Self>>>,
level: usize,
cast: Arc<RwLock<ObserverBroadcast<dyn TerminalView>>>
}
impl LeveledTermView {
pub fn new(
src: OuterViewPort<dyn TerminalView>
) -> (Arc<RwLock<Self>>, OuterViewPort<dyn TerminalView>) {
let port = ViewPort::new();
let v = Self::with_port(src, port.inner());
(v, port.into_outer())
}
pub fn with_port(
src_port: OuterViewPort<dyn TerminalView>,
dst_port: InnerViewPort<dyn TerminalView>
) -> Arc<RwLock<Self>> {
let src_obs = ProjectionArg::new(
// we simply forward all messages
|s: Arc<RwLock<Self>>, msg: &Point2<i16>| {
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<i16>;
type Value = TerminalAtom;
fn get(&self, pos: &Point2<i16>) -> Option<TerminalAtom> {
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<Vec<Point2<i16>>> {
self.src.area()
}
}

View file

@ -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<i16>) -> Option<TerminalAtom> {
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 {
}
}

86
src/projection.rs Normal file
View file

@ -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<V, P>
where V: View + ?Sized,
P: Send + Sync {
pub src: Arc<RwLock<Option<Arc<V>>>>,
pub proj: Weak<RwLock<P>>,
notify_fn: Box<dyn Fn(Arc<RwLock<P>>, &V::Msg) + Send + Sync>
}
impl<V, P> ProjectionArg<V, P>
where V: View + ?Sized,
P: Send + Sync {
pub fn new(f: impl Fn(Arc<RwLock<P>>, &V::Msg) + Send + Sync + 'static) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(ProjectionArg {
src: Arc::new(RwLock::new(None)),
proj: Weak::new(),
notify_fn: Box::new(f)
}))
}
}
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
impl<Item, P> Observer<dyn SingletonView<Item = Item>> for ProjectionArg<dyn SingletonView<Item = Item>, P>
where P: Send + Sync {
fn reset(&mut self, new_src: Option<Arc<dyn SingletonView<Item = Item>>>) {
*self.src.write().unwrap() = new_src;
self.notify(&());
}
fn notify(&self, msg: &()) {
(self.notify_fn)(self.proj.upgrade().unwrap(), msg);
}
}
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
impl<Item, P> Observer<dyn SequenceView<Item = Item>> for ProjectionArg<dyn SequenceView<Item = Item>, P>
where P: Send + Sync {
fn reset(&mut self, new_src: Option<Arc<dyn SequenceView<Item = Item>>>) {
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<Key, Item, P> Observer<dyn IndexView<Key, Item = Item>> for ProjectionArg<dyn IndexView<Key, Item = Item>, P>
where P: Send + Sync {
fn reset(&mut self, new_src: Option<Arc<dyn IndexView<Key, Item = Item>>>) {
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);
}
}

View file

@ -20,7 +20,7 @@ pub trait SingletonView : View<Msg = ()> {
//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
impl<V: SingletonView> SingletonView for RwLock<V> {
impl<V: SingletonView + ?Sized> SingletonView for RwLock<V> {
type Item = V::Item;
fn get(&self) -> Self::Item {
@ -28,7 +28,7 @@ impl<V: SingletonView> SingletonView for RwLock<V> {
}
}
impl<V: SingletonView> SingletonView for Arc<V> {
impl<V: SingletonView + ?Sized> SingletonView for Arc<V> {
type Item = V::Item;
fn get(&self) -> Self::Item {
@ -36,6 +36,19 @@ impl<V: SingletonView> SingletonView for Arc<V> {
}
}
impl<V: SingletonView> SingletonView for Option<V>
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 {

View file

@ -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<dyn TerminalView> {
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<RwLock<Vec<char>>> {
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<Arc<dyn SingletonView<Item = usize>>>,
edit: Weak<RwLock<StringEditView>>
}
pub struct StringInsertView {
_cursor_obs: Arc<RwLock<ProjectionArg<dyn SingletonView<Item = usize>, Self>>>,
_data_obs: Arc<RwLock<ProjectionArg<dyn SequenceView<Item = char>, Self>>>,
impl Observer<dyn SingletonView<Item = usize>> for CursorObserver {
fn reset(&mut self, new_cursor: Option<Arc<dyn SingletonView<Item = usize>>>) {
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<Arc<dyn SequenceView<Item = char>>>,
edit: Weak<RwLock<StringEditView>>
}
impl Observer<dyn SequenceView<Item = char>> for DataObserver {
fn reset(&mut self, new_data: Option<Arc<dyn SequenceView<Item = char>>>) {
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<Arc<RwLock<DataObserver>>>,
cursor_obs: Option<Arc<RwLock<CursorObserver>>>,
cursor: Arc<dyn SingletonView<Item = usize>>,
data: Arc<dyn SequenceView<Item = char>>,
cur_pos: usize,
cast: Arc<RwLock<ObserverBroadcast<dyn TerminalView>>>
}
impl StringEditView {
pub fn new(
cursor_port: OuterViewPort<dyn SingletonView<Item = usize>>,
data_port: OuterViewPort<dyn SequenceView<Item = char>>,
out_port: InnerViewPort<dyn TerminalView>
) -> Arc<RwLock<Self>> {
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<i16>;
}
impl ImplIndexView for StringEditView {
type Key = Point2<i16>;
type Value = TerminalAtom;
impl IndexView<Point2<i16>> for StringInsertView {
type Item = TerminalAtom;
fn get(&self, pos: &Point2<i16>) -> Option<TerminalAtom> {
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<Vec<Point2<i16>>> {
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<dyn SingletonView<Item = usize>>,
data_port: OuterViewPort<dyn SequenceView<Item = char>>,
out_port: InnerViewPort<dyn TerminalView>
) -> Arc<RwLock<Self>> {
let cursor_obs = ProjectionArg::new(
|s: Arc<RwLock<Self>>, _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<RwLock<Self>>, 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
}
}
}