From 62f3a857367e4b414a5b69c20778e1f8aa47748f Mon Sep 17 00:00:00 2001
From: Michael Sippel <micha@fragmental.art>
Date: Wed, 9 Dec 2020 12:56:38 +0100
Subject: [PATCH] add terminal

---
 src/main.rs              | 32 +++++--------
 src/terminal/atom.rs     | 56 +++++++++++++++++++++++
 src/terminal/mod.rs      | 10 ++++
 src/terminal/style.rs    | 87 +++++++++++++++++++++++++++++++++++
 src/terminal/terminal.rs | 99 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 264 insertions(+), 20 deletions(-)
 create mode 100644 src/terminal/atom.rs
 create mode 100644 src/terminal/mod.rs
 create mode 100644 src/terminal/style.rs
 create mode 100644 src/terminal/terminal.rs

diff --git a/src/main.rs b/src/main.rs
index 4593455..dbfdeb6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,7 @@ pub mod port;
 pub mod channel;
 pub mod singleton_buffer;
 pub mod vec_buffer;
+pub mod terminal;
 
 use {
     async_std::{
@@ -20,7 +21,8 @@ use {
         view::{View, Observer},
         port::{InnerViewPort, OuterViewPort},
         singleton_buffer::SingletonBuffer,
-        vec_buffer::VecBuffer
+        vec_buffer::VecBuffer,
+        terminal::{Terminal, TerminalAtom, TerminalStyle}
     }
 };
 
@@ -30,50 +32,40 @@ async fn main() {
     let mut buf = VecBuffer::new(digits.inner());
 
     let digit_view = digits.outer()
-
         // digit encoding
         .map_value(
             |digit|
             if let Some(digit) = digit {
-                char::from_digit(digit, 16)
+                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 view = digit_view.get_view();
-    let mut stream = digit_view.stream().map({
-        move |idx| (idx, view.view(idx))
-    });
-
-    let fut = task::spawn({
-        async move {
-            while let Some((idx, val)) = stream.next().await {
-                println!("v[{:?}] = {:?}", idx, val);
-            }
-            println!("end print task");
-        }
-    });
+    let fut = task::spawn(Terminal::show(digit_view));
 
+    task::sleep(std::time::Duration::from_secs(1)).await;
     buf.push(0);
-    buf.push(1);
+    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);
-    drop(digit_view);
 
     fut.await;
 }
 
-
diff --git a/src/terminal/atom.rs b/src/terminal/atom.rs
new file mode 100644
index 0000000..1cf48e9
--- /dev/null
+++ b/src/terminal/atom.rs
@@ -0,0 +1,56 @@
+
+use super::TerminalStyle;
+
+#[derive(Clone, Copy)]
+pub struct TerminalAtom {
+    pub c: Option<char>,
+    pub style: TerminalStyle
+}
+
+impl TerminalAtom {
+    pub fn new(c: char, style: TerminalStyle) -> Self {
+        TerminalAtom { c: Some(c), style }
+    }
+
+    pub fn new_bg(bg_color: (u8, u8, u8)) -> Self {
+        TerminalAtom { c: None, style: TerminalStyle::bg_color(bg_color) }
+    }
+
+    pub fn add_style_front(mut self, style: TerminalStyle) -> Self {
+        self.style = self.style.add(style);
+        self
+    }
+
+    pub fn add_style_back(mut self, style: TerminalStyle) -> Self {
+        self.style = style.add(self.style);
+        self
+    }
+}
+
+impl From<char> for TerminalAtom {
+    fn from(c: char) -> Self {
+        TerminalAtom {
+            c: Some(c),
+            style: TerminalStyle::default()
+        }
+    }
+}
+
+impl From<Option<char>> for TerminalAtom {
+    fn from(c: Option<char>) -> Self {
+        TerminalAtom {
+            c,
+            style: TerminalStyle::default()
+        }
+    }
+}
+
+impl From<&char> for TerminalAtom {
+    fn from(c: &char) -> Self {
+        TerminalAtom {
+            c: Some(*c),
+            style: TerminalStyle::default()
+        }
+    }
+}
+
diff --git a/src/terminal/mod.rs b/src/terminal/mod.rs
new file mode 100644
index 0000000..55daf75
--- /dev/null
+++ b/src/terminal/mod.rs
@@ -0,0 +1,10 @@
+pub mod style;
+pub mod atom;
+pub mod terminal;
+
+pub use {
+    style::{TerminalStyle},
+    atom::{TerminalAtom},
+    terminal::{Terminal, TerminalEvent},
+};
+
diff --git a/src/terminal/style.rs b/src/terminal/style.rs
new file mode 100644
index 0000000..3e9d6eb
--- /dev/null
+++ b/src/terminal/style.rs
@@ -0,0 +1,87 @@
+
+#[derive(Default, Copy, Clone, PartialEq)]
+pub struct TerminalStyle {
+    pub fg_color: Option<(u8, u8, u8)>,
+    pub bg_color: Option<(u8, u8, u8)>,
+    pub bold: Option<bool>,
+    pub italic: Option<bool>,
+    pub underline: Option<bool>
+}
+
+impl TerminalStyle {
+    pub fn add(&self, mut dominant: TerminalStyle) -> Self {
+        if dominant.fg_color == None {
+            dominant.fg_color = self.fg_color;
+        }
+        if dominant.bg_color == None {
+            dominant.bg_color = self.bg_color;
+        }
+        if dominant.bold == None {
+            dominant.bold = self.bold;
+        }
+        if dominant.italic == None {
+            dominant.italic = self.italic;
+        }
+        if dominant.underline == None {
+            dominant.underline = self.underline;
+        }
+        dominant
+    }
+
+    pub fn fg_color(rgb: (u8, u8, u8)) -> Self {
+        let mut style = TerminalStyle::default();
+        style.fg_color = Some(rgb);
+        style
+    }
+
+    pub fn bg_color(rgb: (u8, u8, u8)) -> Self {
+        let mut style = TerminalStyle::default();
+        style.bg_color = Some(rgb);
+        style
+    }
+
+    pub fn bold(b: bool) -> Self {
+        let mut style = TerminalStyle::default();
+        style.bold = Some(b);
+        style
+    }
+
+    pub fn italic(i: bool) -> Self {
+        let mut style = TerminalStyle::default();
+        style.italic = Some(i);
+        style
+    }
+
+    pub fn underline(u: bool) -> Self {
+        let mut style = TerminalStyle::default();
+        style.underline = Some(u);
+        style
+    }
+}
+
+impl std::fmt::Display for TerminalStyle {
+    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+        match self.fg_color {
+            Some((r, g, b)) => write!(fmt, "{}", termion::color::Fg(termion::color::Rgb(r, g, b)))?,
+            None => write!(fmt, "{}", termion::color::Fg(termion::color::Reset))?,
+        };
+        match self.bg_color {
+            Some((r, g, b)) => write!(fmt, "{}", termion::color::Bg(termion::color::Rgb(r, g, b)))?,
+            None => write!(fmt, "{}", termion::color::Bg(termion::color::Reset))?,
+        };
+        match self.bold {
+            Some(true) => write!(fmt, "{}", termion::style::Bold)?,
+            _ => write!(fmt, "{}", termion::style::NoBold)?,
+        };
+        match self.italic {
+            Some(true) => write!(fmt, "{}", termion::style::Italic)?,
+            _ => write!(fmt, "{}", termion::style::NoItalic)?,
+        };
+        match self.underline {
+            Some(true) => write!(fmt, "{}", termion::style::Underline)?,
+            _ => write!(fmt, "{}", termion::style::NoUnderline)?,
+        };
+        Ok(())
+    }
+}
+
diff --git a/src/terminal/terminal.rs b/src/terminal/terminal.rs
new file mode 100644
index 0000000..f96457d
--- /dev/null
+++ b/src/terminal/terminal.rs
@@ -0,0 +1,99 @@
+
+use {
+    std::io::{Write, stdout, stdin},
+    async_std::stream::{Stream, StreamExt},
+    cgmath::Vector2,
+    termion::{
+        raw::IntoRawMode,
+        input::{TermRead, MouseTerminal}
+    },
+    super::{TerminalAtom, TerminalStyle},
+    crate::{
+        view::{View, Observer},
+        port::{OuterViewPort},
+        channel::ChannelReceiver
+    }
+};
+
+pub enum TerminalEvent {
+    Resize((u16, u16)),
+    Input(termion::event::Event)
+}
+
+pub struct Terminal {
+    events: ChannelReceiver<Vec<TerminalEvent>>
+}
+
+impl Terminal {
+    pub fn new() -> Self {
+        let (event_tx, event_rx) = crate::channel::queue_channel();
+
+        let input_tx = event_tx.clone();
+        std::thread::spawn(move || {
+            for event in stdin().events() {
+                input_tx.notify(TerminalEvent::Input(event.unwrap()));
+            }
+        });
+/*
+        let mut resize_stream = signal(tokio::signal::unix::SignalKind::window_change()).unwrap();
+        let resize_tx = event_tx.clone();
+        tokio::spawn(async move {
+            loop {
+                resize_stream.recv().await;
+                resize_tx.send(TerminalEvent::Resize(termion::terminal_size().unwrap()));
+            }
+        });
+*/
+        Terminal {
+            events: event_rx
+        }
+    }
+
+    pub async fn show(view_port: OuterViewPort<Vector2<i16>, TerminalAtom>) -> std::io::Result<()> {
+        let (atom_tx, atom_rx) = crate::channel::queue_channel();
+
+        let view = view_port.get_view();
+        view_port.add_observer_fn(move |pos| atom_tx.notify((pos, view.view(pos))));
+
+        Self::show_stream(atom_rx).await
+    }
+
+    pub async fn show_stream(recv: ChannelReceiver<Vec<(Vector2<i16>, Option<TerminalAtom>)>>) -> std::io::Result<()> {
+        let mut out = MouseTerminal::from(stdout().into_raw_mode().unwrap());
+        let mut cur_pos = Vector2::<i16>::new(0, 0);
+        let mut cur_style = TerminalStyle::default();
+            write!(out, "{}{}{}{}",
+                   termion::clear::All,
+                   termion::cursor::Goto(1, 1),
+                   termion::cursor::Hide,
+                   termion::cursor::Goto(1, 1))?;
+
+        while let Some(atoms) = recv.recv().await {
+            for (pos, atom) in atoms.into_iter() {
+                if pos != cur_pos {
+                    write!(out, "{}", termion::cursor::Goto(pos.x as u16 + 1, pos.y as u16 + 1))?;
+                }
+                cur_pos = pos;
+
+                if let Some(atom) = atom {
+                    if cur_style != atom.style {
+                        cur_style = atom.style;
+                        write!(out, "{}", atom.style)?;
+                    }
+
+                    write!(out, "{}", atom.c.unwrap_or(' '))?;
+                } else {
+                    write!(out, "{} ", termion::style::Reset);
+                }
+            }
+
+            out.flush()?;
+        }
+
+        write!(out, "{}", termion::cursor::Show)?;
+        out.flush()?;
+
+        std::io::Result::Ok(())        
+    }
+}
+