use { r3vi::{ view::{ channel::{queue_channel, set_channel, ChannelReceiver, ChannelSender}, Observer, OuterViewPort, grid::*, index::*, } }, crate::atom::{TerminalStyle}, crate::{TerminalView}, async_std::{stream::StreamExt, task}, cgmath::{Point2, Vector2}, signal_hook, signal_hook_async_std::Signals, std::sync::RwLock, std::{ collections::HashSet, io::{stdin, stdout, Write}, sync::Arc, }, termion::{ input::{MouseTerminal, TermRead}, raw::IntoRawMode, }, }; #[derive(PartialEq, Eq, Clone, Debug)] pub enum TerminalEvent { Resize(Vector2<i16>), Input(termion::event::Event), } pub struct Terminal { writer: Arc<TermOutWriter>, _observer: Arc<RwLock<TermOutObserver>>, events: ChannelReceiver<Vec<TerminalEvent>>, _signal_handle: signal_hook_async_std::Handle, } impl Terminal { pub fn new(port: OuterViewPort<dyn TerminalView>) -> Self { let (dirty_pos_tx, dirty_pos_rx) = set_channel(); let writer = Arc::new(TermOutWriter { out: RwLock::new(MouseTerminal::from(stdout().into_raw_mode().unwrap())), dirty_pos_rx, view: port.get_view_arc(), }); let observer = Arc::new(RwLock::new(TermOutObserver { dirty_pos_tx, writer: writer.clone(), })); port.add_observer(observer.clone()); let (event_tx, event_rx) = queue_channel(); let input_tx = event_tx.clone(); std::thread::spawn(move || { for event in stdin().events() { input_tx.send(TerminalEvent::Input(event.unwrap())); } }); // send initial teriminal size let (w, h) = termion::terminal_size().unwrap(); event_tx.send(TerminalEvent::Resize(Vector2::new(w as i16, h as i16))); // and again on SIGWINCH let signals = Signals::new(&[signal_hook::consts::signal::SIGWINCH]).unwrap(); let handle = signals.handle(); task::spawn(async move { let mut signals = signals.fuse(); while let Some(signal) = signals.next().await { match signal { signal_hook::consts::signal::SIGWINCH => { let (w, h) = termion::terminal_size().unwrap(); event_tx.send(TerminalEvent::Resize(Vector2::new(w as i16, h as i16))); } _ => unreachable!(), } } }); Terminal { writer, _observer: observer, events: event_rx, _signal_handle: handle, } } pub fn get_writer(&self) -> Arc<TermOutWriter> { self.writer.clone() } pub async fn next_event(&mut self) -> TerminalEvent { self.events.next().await.unwrap() } } struct TermOutObserver { dirty_pos_tx: ChannelSender<HashSet<Point2<i16>>>, writer: Arc<TermOutWriter>, } impl TermOutObserver { fn send_area(&mut self, area: IndexArea<Point2<i16>>) { match area { IndexArea::Empty => {} IndexArea::Full => { let (w, h) = termion::terminal_size().unwrap(); for pos in GridWindowIterator::from(Point2::new(0, 0)..Point2::new(w as i16, h as i16)) { self.dirty_pos_tx.send(pos); } } IndexArea::Range(r) => { for pos in GridWindowIterator::from(r) { self.dirty_pos_tx.send(pos); } } IndexArea::Set(v) => { for pos in v { self.dirty_pos_tx.send(pos); } } } } } impl Observer<dyn TerminalView> for TermOutObserver { fn reset(&mut self, view: Option<Arc<dyn TerminalView>>) { self.writer.reset(); if let Some(view) = view { self.send_area(view.area()); } } fn notify(&mut self, area: &IndexArea<Point2<i16>>) { self.send_area(area.clone()); } } pub struct TermOutWriter { out: RwLock<MouseTerminal<termion::raw::RawTerminal<std::io::Stdout>>>, dirty_pos_rx: ChannelReceiver<HashSet<Point2<i16>>>, view: Arc<RwLock<Option<Arc<dyn TerminalView>>>>, } impl TermOutWriter { fn reset(&self) { let mut out = self.out.write().unwrap(); write!(out, "{}", termion::clear::All).ok(); } } impl TermOutWriter { pub async fn show(&self) -> std::io::Result<()> { // init write!( self.out.write().unwrap(), "{}{}{}", termion::cursor::Hide, termion::cursor::Goto(1, 1), termion::style::Reset )?; let mut cur_pos = Point2::<i16>::new(0, 0); let mut cur_style = TerminalStyle::default(); // draw atoms until view port is destroyed while let Some(dirty_pos) = self.dirty_pos_rx.recv().await { let (w, _h) = termion::terminal_size().unwrap(); if let Some(view) = self.view.read().unwrap().as_ref() { let mut out = self.out.write().unwrap(); let d = dirty_pos .into_iter() .filter(|p| p.x >= 0 && p.y >= 0 && p.x < w as i16 && p.y < w as i16); //.collect::<Vec<_>>(); /* d.sort_by(|a,b| { if a.y < b.y { std::cmp::Ordering::Less } else if a.y == b.y { a.x.cmp(&b.x) } else { std::cmp::Ordering::Greater } }); */ for pos in d { if pos != cur_pos { write!( out, "{}", termion::cursor::Goto(pos.x as u16 + 1, pos.y as u16 + 1) )?; } if let Some(atom) = view.get(&pos) { if cur_style != atom.style { cur_style = atom.style; write!(out, "{}", termion::style::Reset)?; write!(out, "{}", atom.style)?; } write!(out, "{}", atom.c.unwrap_or(' '))?; } else { write!(out, "{} ", termion::style::Reset)?; cur_style = TerminalStyle::default(); } cur_pos = pos + Vector2::new(1, 0); } out.flush()?; } } // restore conventional terminal settings let mut out = self.out.write().unwrap(); write!(out, "{}", termion::cursor::Show)?; out.flush()?; std::io::Result::Ok(()) } }