prayterm/
lib.rs

1#![doc(html_root_url = "https://docs.rs/prayterm/1.0.1")]
2//! prayterm realtime play nonblocking terminal for Rust with crossterm
3//!
4
5use std::fmt;
6use std::error::Error;
7use std::io::{stdout, Write};
8use std::time;
9use std::thread;
10use std::sync::mpsc;
11
12use crossterm::{execute, queue};
13use crossterm::terminal::{self, disable_raw_mode, enable_raw_mode};
14use crossterm::cursor;
15use crossterm::style::{self, Attribute};
16use crossterm::event::{self, Event};
17
18/// tuple TRX for mpsc::channel Result termion Event (not std::error::Error)
19pub type TplTRX = (
20  mpsc::Sender<Result<Event, std::io::Error>>,
21  mpsc::Receiver<Result<Event, std::io::Error>>);
22
23/// NopColor
24pub trait NopColor {
25  /// nop
26  fn nop(&self) -> style::Color;
27}
28
29/// NopColor for style::Color
30impl NopColor for style::Color {
31  /// nop
32  fn nop(&self) -> style::Color { *self }
33}
34
35/// Rgb
36#[derive(Debug, Clone)]
37pub struct Rgb(pub u8, pub u8, pub u8);
38
39/// NopColor for Rgb
40impl NopColor for Rgb {
41  /// nop
42  fn nop(&self) -> style::Color {
43    style::Color::Rgb{r: self.0, g: self.1, b: self.2}
44  }
45}
46
47/// PrayTerm
48// #[derive(Debug)]
49pub struct PrayTerm {
50  /// kind
51  pub k: u16,
52  /// width
53  pub w: u16,
54  /// height
55  pub h: u16,
56  /// so stdout
57  pub so: Box<dyn Write>
58}
59
60/// Debug
61impl fmt::Debug for PrayTerm {
62  /// fmt
63  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64    write!(f, "({}, {}) [stdout]", self.w, self.h)
65  }
66}
67
68/// Display
69impl fmt::Display for PrayTerm {
70  /// fmt
71  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72    write!(f, "{:?}", self)
73  }
74}
75
76/// PrayTerm
77impl PrayTerm {
78  /// constructor
79  pub fn new(k: u16) -> Result<Self, Box<dyn Error>> {
80    let (w, h) = terminal::size()?;
81    enable_raw_mode()?;
82    let mut so = stdout();
83    if k & 5 != 0 { execute!(so, terminal::EnterAlternateScreen)?; }
84    if k & 6 != 0 { execute!(so, event::EnableMouseCapture)?; }
85    Ok(PrayTerm{k, w, h, so: Box::new(so)})
86  }
87
88  /// begin
89  pub fn begin(&mut self) -> Result<(), Box<dyn Error>> {
90    execute!(self.so,
91      cursor::SetCursorStyle::DefaultUserShape, // Blinking... Steady...
92      cursor::Hide,
93      terminal::Clear(terminal::ClearType::All))?;
94    Ok(())
95  }
96
97  /// fin
98  pub fn fin(&mut self) -> Result<(), Box<dyn Error>> {
99    execute!(self.so,
100      cursor::SetCursorStyle::BlinkingUnderScore, // Block[] UnderScore_ Bar|
101      cursor::Show)?;
102    if self.k & 6 != 0 { execute!(self.so, event::DisableMouseCapture)?; }
103    if self.k & 5 != 0 { execute!(self.so, terminal::LeaveAlternateScreen)?; }
104    disable_raw_mode()?;
105    Ok(())
106  }
107
108  /// style
109  pub fn style(&mut self, s: Attribute) -> Result<(), Box<dyn Error>> {
110    queue!(self.so, style::SetAttribute(s))?;
111    Ok(())
112  }
113
114  /// write
115  pub fn wr(&mut self, x: u16, y: u16,
116    st: u16, bg: impl NopColor, fg: impl NopColor, msg: &String) ->
117    Result<(), Box<dyn Error>> {
118    let styles: Vec<Attribute> = vec![Attribute::Bold, Attribute::Italic];
119    for (i, s) in styles.iter().enumerate() {
120      if st & 2^(i as u16) != 0 { self.style(*s)?; }
121    }
122    queue!(self.so,
123      cursor::MoveTo(x, y),
124      style::SetBackgroundColor(bg.nop()), style::SetForegroundColor(fg.nop()),
125      style::Print(msg), style::ResetColor)?;
126    self.so.flush()?;
127    Ok(())
128  }
129
130  /// prepare thread
131  pub fn prepare_thread(&self, ms: time::Duration) ->
132    Result<TplTRX, Box<dyn Error>> {
133    let (tx, rx) = mpsc::channel();
134    if true { // closure once
135      let tx = tx.clone();
136      let _handle = thread::spawn(move || { // for non blocking to fetch event
137        loop { // loop forever
138          if !event::poll(ms).expect("poll") { () } // non blocking
139          else { tx.send(event::read()).expect("send"); } // blocking
140        }
141        // () // not be arrived here (will not be disconnected)
142      });
143    }
144    Ok((tx, rx))
145  }
146}
147
148/// test with [-- --nocapture] or [-- --show-output]
149#[cfg(test)]
150mod tests {
151  use super::{PrayTerm, Rgb};
152  use crossterm::style::Color;
153
154  /// test a
155  #[test]
156  fn test_a() {
157    let s = String::from_utf8("ABC".into()).expect("utf8");
158    let mut tm = PrayTerm::new(2).expect("construct");
159    tm.begin().expect("begin");
160    tm.wr(0, 48, 3, Color::Blue, Color::Yellow, &s).expect("wr");
161    tm.wr(0, 49, 3, Rgb(240, 192, 32), Rgb(240, 32, 192), &s).expect("wr");
162    tm.fin().expect("fin");
163    assert_eq!(tm.w, 80);
164    assert_eq!(tm.h, 50);
165  }
166}