1#![doc(html_root_url = "https://docs.rs/prayterm/1.0.1")]
2use 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
18pub type TplTRX = (
20 mpsc::Sender<Result<Event, std::io::Error>>,
21 mpsc::Receiver<Result<Event, std::io::Error>>);
22
23pub trait NopColor {
25 fn nop(&self) -> style::Color;
27}
28
29impl NopColor for style::Color {
31 fn nop(&self) -> style::Color { *self }
33}
34
35#[derive(Debug, Clone)]
37pub struct Rgb(pub u8, pub u8, pub u8);
38
39impl NopColor for Rgb {
41 fn nop(&self) -> style::Color {
43 style::Color::Rgb{r: self.0, g: self.1, b: self.2}
44 }
45}
46
47pub struct PrayTerm {
50 pub k: u16,
52 pub w: u16,
54 pub h: u16,
56 pub so: Box<dyn Write>
58}
59
60impl fmt::Debug for PrayTerm {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 write!(f, "({}, {}) [stdout]", self.w, self.h)
65 }
66}
67
68impl fmt::Display for PrayTerm {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72 write!(f, "{:?}", self)
73 }
74}
75
76impl PrayTerm {
78 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 pub fn begin(&mut self) -> Result<(), Box<dyn Error>> {
90 execute!(self.so,
91 cursor::SetCursorStyle::DefaultUserShape, cursor::Hide,
93 terminal::Clear(terminal::ClearType::All))?;
94 Ok(())
95 }
96
97 pub fn fin(&mut self) -> Result<(), Box<dyn Error>> {
99 execute!(self.so,
100 cursor::SetCursorStyle::BlinkingUnderScore, 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 pub fn style(&mut self, s: Attribute) -> Result<(), Box<dyn Error>> {
110 queue!(self.so, style::SetAttribute(s))?;
111 Ok(())
112 }
113
114 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 pub fn prepare_thread(&self, ms: time::Duration) ->
132 Result<TplTRX, Box<dyn Error>> {
133 let (tx, rx) = mpsc::channel();
134 if true { let tx = tx.clone();
136 let _handle = thread::spawn(move || { loop { if !event::poll(ms).expect("poll") { () } else { tx.send(event::read()).expect("send"); } }
141 });
143 }
144 Ok((tx, rx))
145 }
146}
147
148#[cfg(test)]
150mod tests {
151 use super::{PrayTerm, Rgb};
152 use crossterm::style::Color;
153
154 #[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}