From 4a29d7a5cf28c4bf0f197f38144ccc432b1a4241 Mon Sep 17 00:00:00 2001
From: Michael Sippel <micha@fragmental.art>
Date: Wed, 9 Dec 2020 17:31:08 +0100
Subject: [PATCH] add TerminalCompositor

and some nice utilities for ports
---
 src/main.rs                | 114 +++++++++++++++++++++++--------------
 src/port.rs                |  17 +++++-
 src/terminal/compositor.rs |  53 +++++++++++++++++
 src/terminal/mod.rs        |   2 +
 4 files changed, 143 insertions(+), 43 deletions(-)
 create mode 100644 src/terminal/compositor.rs

diff --git a/src/main.rs b/src/main.rs
index dbfdeb6..99c0644 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -19,53 +19,83 @@ use {
     cgmath::{Vector2},
     crate::{
         view::{View, Observer},
-        port::{InnerViewPort, OuterViewPort},
+        port::{ViewPort, InnerViewPort, OuterViewPort},
         singleton_buffer::SingletonBuffer,
         vec_buffer::VecBuffer,
-        terminal::{Terminal, TerminalAtom, TerminalStyle}
+        terminal::{Terminal, TerminalAtom, TerminalStyle, TerminalCompositor}
     }
 };
 
-#[async_std::main]
-async fn main() {
-    let digits = port::ViewPort::new();
-    let mut buf = VecBuffer::new(digits.inner());
+struct Fill(TerminalAtom);
+impl View for Fill {
+    type Key = Vector2<i16>;
+    type Value = TerminalAtom;
 
-    let digit_view = digits.outer()
-        // digit encoding
-        .map_value(
-            |digit|
-            if let Some(digit) = digit {
-                Some(TerminalAtom::new(char::from_digit(digit, 16).unwrap(), TerminalStyle::bg_color((100,30,30))))
-            } else {
-                None
-            }
-        )
-        // simple horizontal layout
-        .map_key(
-            |idx| Vector2::<i16>::new(idx as i16, 0),
-            |pos| pos.x as usize
-        );
-
-    let fut = task::spawn(Terminal::show(digit_view));
-
-    task::sleep(std::time::Duration::from_secs(1)).await;
-    buf.push(0);
-    buf.push(10);
-    task::sleep(std::time::Duration::from_secs(1)).await;
-    buf.push(2);
-    buf.push(3);
-    task::sleep(std::time::Duration::from_secs(1)).await;
-    buf.push(4);
-    task::sleep(std::time::Duration::from_secs(1)).await;
-    buf.insert(0, 15);
-    task::sleep(std::time::Duration::from_secs(1)).await;
-    buf.remove(2);
-    task::sleep(std::time::Duration::from_secs(1)).await;
-
-    drop(buf);
-    drop(digits);
-
-    fut.await;
+    fn view(&self, _: Vector2<i16>) -> Option<TerminalAtom> {
+        Some(self.0.clone())
+    }
+}
+
+#[async_std::main]
+async fn main() {
+    let composite_view = port::ViewPort::new();
+    let mut compositor = TerminalCompositor::new(composite_view.inner());
+
+    task::spawn(async move {
+        // background
+        let fp = port::ViewPort::with_view(Arc::new(Fill(TerminalAtom::new('.', TerminalStyle::fg_color((50,50,50))))));
+        compositor.push(fp.outer());
+
+        // view of Vec<u32>
+        let digits = port::ViewPort::new();
+        let mut buf = VecBuffer::new(digits.inner());
+        compositor.push(
+            digits.outer()
+                .map_value( // digit encoding
+                    |digit|
+                    if let Some(digit) = digit {
+                        Some(TerminalAtom::new(
+                            char::from_digit(digit, 16).unwrap(),
+                            TerminalStyle::bg_color((100,30,30)).add(
+                                TerminalStyle::fg_color((255,255,255)))))
+                    } else {
+                        None
+                    }
+                )
+                .map_key( // a lightly tilted layout
+                    // mapping from index to position in 2D-grid
+                    |idx| Vector2::<i16>::new(idx as i16, idx as i16 / 2),
+                    // reverse mapping from position to idx
+                    |pos| pos.x as usize
+                ));
+
+        // TODO: use the real terminal size...
+        for x in 0 .. 10 {
+            for y in 0 .. 10 {
+                fp.inner().notify(Vector2::new(x,y));
+            }
+        }
+
+        // now some modifications on our VecBuffer, which will automatically update the View
+        buf.push(0);
+        buf.push(10);
+        task::sleep(std::time::Duration::from_millis(400)).await;
+        buf.push(2);
+        buf.push(3);
+        task::sleep(std::time::Duration::from_millis(400)).await;
+        buf.push(4);
+        task::sleep(std::time::Duration::from_millis(400)).await;
+        buf.insert(0, 15);
+        task::sleep(std::time::Duration::from_millis(400)).await;
+        buf.remove(2);
+        task::sleep(std::time::Duration::from_millis(400)).await;
+
+        for x in 0 .. 4 {
+            buf.remove(0);
+            task::sleep(std::time::Duration::from_millis(400)).await;
+        }
+    });
+
+    Terminal::show(composite_view.into_outer()).await;
 }
 
diff --git a/src/port.rs b/src/port.rs
index 5874217..2980068 100644
--- a/src/port.rs
+++ b/src/port.rs
@@ -31,6 +31,13 @@ where K: Send + Sync + 'static,
         }
     }
 
+    pub fn with_view(view: Arc<dyn View<Key = K, Value = V>>) -> Self {
+        ViewPort {
+            view: Arc::new(RwLock::new(Some(view))),
+            observers: Arc::new(RwLock::new(Vec::new()))
+        }
+    }
+
     pub fn set_view(&self, view: Arc<dyn View<Key = K, Value = V>>) {
         *self.view.write().unwrap() = Some(view);
     }
@@ -46,6 +53,14 @@ where K: Send + Sync + 'static,
     pub fn outer(&self) -> OuterViewPort<K, V> {
         OuterViewPort(ViewPort{ view: self.view.clone(), observers: self.observers.clone() })
     }
+
+    pub fn into_inner(self) -> InnerViewPort<K, V> {
+        InnerViewPort(ViewPort{ view: self.view.clone(), observers: self.observers.clone() })
+    }
+
+    pub fn into_outer(self) -> OuterViewPort<K, V> {
+        OuterViewPort(ViewPort{ view: self.view.clone(), observers: self.observers.clone() })
+    }
 }
 
 //<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>
@@ -74,7 +89,7 @@ impl<K: Send + Sync + 'static, V: Send + Sync + 'static> OuterViewPort<K, V> {
 }
 
 impl<K: Eq + Hash + Send + Sync + 'static, V: Send + Sync + 'static> OuterViewPort<K, V> {
-    pub fn stream(&self) -> ChannelReceiver<HashSet<K>> {
+    pub fn stream(self) -> ChannelReceiver<HashSet<K>> {
         let (s, r) = crate::channel::set_channel();
         self.0.add_observer(Arc::new(s));
         r
diff --git a/src/terminal/compositor.rs b/src/terminal/compositor.rs
new file mode 100644
index 0000000..c60e657
--- /dev/null
+++ b/src/terminal/compositor.rs
@@ -0,0 +1,53 @@
+use {
+    std::sync::{Arc, RwLock},
+    cgmath::Vector2,
+    crate::{
+        view::{View, Observer},
+        port::{ViewPort, InnerViewPort, OuterViewPort},
+        terminal::{TerminalAtom}
+    }
+};
+
+pub struct TerminalCompositor {
+    layers: Arc<RwLock<Vec<Arc<dyn View<Key = Vector2<i16>, Value = TerminalAtom>>>>>,
+    port: Arc<InnerViewPort<Vector2<i16>, TerminalAtom>>
+}
+
+impl TerminalCompositor {
+    pub fn new(port: InnerViewPort<Vector2<i16>, TerminalAtom>) -> Self {
+        let layers = Arc::new(RwLock::new(Vec::<Arc<dyn View<Key = Vector2<i16>, Value = TerminalAtom>>>::new()));
+
+        port.set_view_fn({
+            let layers = layers.clone();
+            move |pos| {
+                let mut atom = None;
+
+                for l in layers.read().unwrap().iter() {
+                    match (atom, l.view(pos)) {
+                        (None, next) => atom = next,
+                        (Some(last), Some(next)) => atom = Some(next.add_style_back(last.style)),
+                        _ => {}
+                    }
+                }
+
+                atom
+            }
+        });
+        
+        TerminalCompositor {
+            layers,
+            port: Arc::new(port)
+        }
+    }
+
+    pub fn push(&mut self, v: OuterViewPort<Vector2<i16>, TerminalAtom>) {
+        self.layers.write().unwrap().push(v.add_observer(self.port.clone()));
+    }
+    
+    pub fn make_port(&mut self) -> InnerViewPort<Vector2<i16>, TerminalAtom> {
+        let port = ViewPort::new();
+        self.push(port.outer());
+        port.inner()
+    }
+}
+
diff --git a/src/terminal/mod.rs b/src/terminal/mod.rs
index 55daf75..3296bc0 100644
--- a/src/terminal/mod.rs
+++ b/src/terminal/mod.rs
@@ -1,10 +1,12 @@
 pub mod style;
 pub mod atom;
 pub mod terminal;
+pub mod compositor;
 
 pub use {
     style::{TerminalStyle},
     atom::{TerminalAtom},
     terminal::{Terminal, TerminalEvent},
+    compositor::TerminalCompositor
 };