multi_window_output/
lib.rs

1//! multi-window-output is a tool for allowing multiple output windows in the same screen. You can
2//! have as many `Screen`s as you please; however, everytime you call `Screen::flush()` or
3//! `Screen::println()`, the current terminal screen will be replaced with the output of the calling screen.
4
5mod window;
6use std::io::Write;
7use termion::color;
8use termion::screen::IntoAlternateScreen;
9use termion::terminal_size;
10use window::{Priority, Window};
11
12const MAX_WIN: usize = 6;
13const INIT: Option<Window> = None;
14const MAX_WIDTH: usize = 512;
15const MAX_HEIGHT: usize = 254;
16
17#[cfg(test)]
18mod screen_tests {
19    use super::*;
20    #[test]
21    fn create_screen() {
22        let mut screen = Screen::new();
23        let id_1 = screen.append_left_child(0).unwrap();
24        let id_2 = screen.append_down_child(0).unwrap();
25        screen.println(id_1, "Hello World").unwrap();
26        screen.println(id_2, "Hello World").unwrap();
27    }
28    #[test]
29    fn bridge_creation() {
30        let mut screen = Screen::name_screen("My New Screen");
31        screen.set_name("Change the name");
32        screen.set_window_name(0, "My only window").unwrap();
33        let bridge = Bridge::new(screen);
34        bridge.println(0, "Hello World").unwrap();
35    }
36    #[test]
37    fn not_found(){
38        let mut screen = Screen::new();
39        let err = screen.flush(3);
40        assert_eq!(Err(std::io::ErrorKind::NotFound), err);
41    }
42    #[test]
43    fn alredy_exists(){
44        let mut screen = Screen::new();
45        screen.append_left_child(0).unwrap();
46        let err = screen.append_left_child(0);
47        assert_eq!(Err(std::io::ErrorKind::AlreadyExists), err);
48    }
49    #[test]
50    fn out_of_memory(){
51        let mut screen = Screen::new();
52        let mut id:usize = 0;
53        for _ in 0..(MAX_WIN - 1) {
54            id = screen.append_left_child(id).unwrap();
55        }
56        let err = screen.append_left_child(0);
57        assert_eq!(Err(std::io::ErrorKind::OutOfMemory), err);
58    }
59}
60
61#[derive(Debug)]
62/// A `Screen` contains multiple windows. When initiated, a `Screen` only contains one window with
63/// `id = 0`.
64pub struct Screen {
65    name: String,
66    windows: [Option<Window>; MAX_WIN],
67    count: usize,
68    buffer: [[(u8, bool); MAX_WIDTH]; MAX_HEIGHT],
69}
70
71impl Screen {
72    /// Create a new `Screen` with one window with `id = 0`.
73    pub fn new() -> Screen {
74        let mut screen = Screen {
75            windows: [INIT; MAX_WIN],
76            count: 1,
77            name: "Screen".to_string(),
78            buffer: [[(b' ', false); MAX_WIDTH]; MAX_HEIGHT],
79        };
80        screen.windows[0] = Some(Window::new(0));
81        screen.load();
82        screen
83    }
84    /// Create a new `Screen` with name
85    pub fn name_screen(name: &str) -> Screen {
86        let mut screen = Screen::new();
87        screen.set_name(name);
88        screen
89    }
90    /// Set name to `Screen`
91    pub fn set_name(&mut self, name: &str) {
92        self.name = name.to_string();
93    }
94    /// Set name to window
95    pub fn set_window_name(&mut self, id: usize, name: &str) -> Result<(), std::io::ErrorKind> {
96        self.validate_id(id)?;
97        self.windows[id].as_mut().unwrap().name = name.to_string();
98        Ok(())
99    }
100    fn load(&mut self) {
101        let mut scr = std::io::stdout().into_alternate_screen().unwrap();
102
103        let (width, height) = terminal_size().unwrap();
104        println!(
105            "{}Screen: {}{}{}",
106            color::Bg(color::Green),
107            self.name,
108            " ".repeat((width - 8 - self.name.len() as u16) as usize),
109            color::Bg(color::Reset)
110        );
111        let width = width as usize;
112        let height = height as usize - 1;
113
114        self.output(0, 0, width, 0, height);
115
116        for i in 0..height {
117            for j in 0..width {
118                if self.buffer[i][j].1 {
119                    print!(
120                        "{}{}{}",
121                        color::Bg(color::Green),
122                        self.buffer[i][j].0 as char,
123                        color::Bg(color::Reset)
124                    );
125                } else {
126                    print!("{}", self.buffer[i][j].0 as char);
127                }
128            }
129        }
130        scr.flush().unwrap();
131    }
132    fn output(
133        &mut self,
134        id: usize,
135        start_width: usize,
136        mut end_width: usize,
137        start_height: usize,
138        mut end_height: usize,
139    ) {
140        match self.windows[id].as_ref().unwrap().priority {
141            Some(Priority::Vertical) => {
142                let mit = (start_width + end_width) / 2;
143                let left_id: usize = *self.windows[id]
144                    .as_ref()
145                    .unwrap()
146                    .left_child
147                    .as_ref()
148                    .unwrap();
149                self.output(left_id, mit, end_width, start_height, end_height);
150                end_width = mit;
151                if let Some(down_id) = self.windows[id].as_ref().unwrap().down_child.as_ref() {
152                    let mit = (start_height + end_height) / 2;
153                    self.output(*down_id, start_width, end_width, mit, end_height);
154                    end_height = mit;
155                }
156            }
157            Some(Priority::Horizontal) => {
158                let mit = (start_height + end_height) / 2;
159                let down_id: usize = *self.windows[id]
160                    .as_ref()
161                    .unwrap()
162                    .down_child
163                    .as_ref()
164                    .unwrap();
165                self.output(down_id, start_width, end_width, mit, end_height);
166                end_height = mit;
167                if let Some(left_id) = self.windows[id].as_ref().unwrap().left_child.as_ref() {
168                    let mit = (start_width + end_width) / 2;
169                    self.output(*left_id, mit, end_width, start_height, end_height);
170                    end_width = mit;
171                }
172            }
173            None => {}
174        }
175
176        let buffer_size = self.windows[id].as_ref().unwrap().buffer.iter().count();
177
178        let mut it = self.windows[id].as_ref().unwrap().buffer.iter();
179        if buffer_size > end_height - start_height - 1 {
180            it.nth(buffer_size - (end_height - start_height))
181                .unwrap()
182                .as_ref()
183                .unwrap();
184        }
185
186        let empty_line = "-- ".to_string();
187        for i in start_height..end_height - 1 {
188            let line = match it.next() {
189                Some(s) => s.as_ref().unwrap(),
190                None => &empty_line,
191            };
192            let mut letter = line.chars();
193            for j in start_width..end_width {
194                if j == end_width - 1 {
195                    self.buffer[i][j] = (b' ', true)
196                } else {
197                    self.buffer[i][j] = (
198                        match letter.next() {
199                            Some(l) => l as u8,
200                            None => b' ',
201                        },
202                        false,
203                    )
204                };
205            }
206        }
207
208        // Put the name in the lower part
209        let name = format!(
210            "{} ID: {}",
211            self.windows[id].as_ref().unwrap().get_name(),
212            id
213        );
214        let mut c = name.chars();
215        for j in start_width..end_width {
216            self.buffer[end_height - 1][j] = (
217                match c.next() {
218                    Some(l) => l as u8,
219                    None => b' ',
220                },
221                true,
222            );
223        }
224    }
225    // Validate existance of window
226    fn validate_id(&self, id: usize) -> Result<(), std::io::ErrorKind> {
227        // Invalid ID
228        if id >= MAX_WIN || self.windows[id].is_none(){
229            return Err(std::io::ErrorKind::NotFound);
230        }
231        Ok(())
232    }
233    /// Splits window with `id` into a left and right window. If successfull, the left window keeps the `id` you
234    /// passed, and the function returns `Ok(id)`, with the `id` of the right window. If failed, returns
235    /// `Err(std::io::ErrorKind)`.
236    pub fn append_left_child(&mut self, id: usize) -> Result<usize, std::io::ErrorKind> {
237        self.append_child(id, Priority::Vertical)
238    }
239    /// Splits window with `id` into an up and down window. If successfull, the up window keeps the `id` you
240    /// passed, and the function returns `Ok(id)`, with the `id` of the down window. If failed, returns
241    /// `Err(std::io::ErrorKind)`.
242    pub fn append_down_child(&mut self, id: usize) -> Result<usize, std::io::ErrorKind> {
243        self.append_child(id, Priority::Horizontal)
244    }
245    fn append_child(&mut self, id: usize, priority: Priority) -> Result<usize, std::io::ErrorKind> {
246        // Not enough space to add new window
247        if self.count >= MAX_WIN {
248            return Err(std::io::ErrorKind::OutOfMemory);
249        }
250        // Validate if child exits
251        self.validate_id(id)?;
252
253        // Validate that node doesn't alredy have child
254        let child = match priority {
255            Priority::Vertical => self.windows[id].as_ref().unwrap().left_child,
256            Priority::Horizontal => self.windows[id].as_ref().unwrap().down_child,
257        };
258        if child.is_some() {
259            return Err(std::io::ErrorKind::AlreadyExists);
260        }
261
262        self.windows[self.count] = Some(Window::new(self.count));
263        match priority {
264            Priority::Vertical => self.windows[id].as_mut().unwrap().left_child = Some(self.count),
265            Priority::Horizontal => {
266                self.windows[id].as_mut().unwrap().down_child = Some(self.count)
267            }
268        };
269        if self.windows[id].as_ref().unwrap().priority.is_none() {
270            self.windows[id].as_mut().unwrap().priority = Some(priority);
271        }
272        self.count += 1;
273        Ok(self.count - 1)
274    }
275    /// Print a new `line` in window with `id`. Returns `()` if successful, `Err(std::io::ErrorKind)` if not.
276    pub fn println(&mut self, id: usize, line: &str) -> Result<(), std::io::ErrorKind> {
277        // Validate if child exits
278        self.validate_id(id)?;
279        self.print(id, line).unwrap();
280        self.flush(id)
281    }
282    /// Print `line` in window with `id`, but do not flush. Returns `()` if successful, `Err(std::io::ErrorKind)` if not.
283    pub fn print(&mut self, id: usize, line: &str) -> Result<(), std::io::ErrorKind> {
284        // Validate if child exits
285        self.validate_id(id)?;
286        self.windows[id].as_mut().unwrap().print(line);
287        Ok(())
288    }
289    /// Flush window with `id`. Returns `()` if successful, `Err(std::io::ErrorKind)` if not.
290    pub fn flush(&mut self, id: usize) -> Result<(), std::io::ErrorKind> {
291        // Validate if child exits
292        self.validate_id(id)?;
293        self.windows[id].as_mut().unwrap().flush();
294        self.load();
295        Ok(())
296    }
297}
298
299impl Default for Screen {
300    fn default() -> Self {
301        Screen::new()
302    }
303}
304enum Cmds {
305    Print,
306    Flush,
307    Println,
308    Break,
309}
310
311/// `Bridge` is a complement for `Screen`. It allows you to print new lines in windows from different threads.
312/// You initiate a `Bridge` by passing a pre-created `Screen`. The only disadvantage of using
313/// `Bridge` is that you can't create new children once you have created the `Bridge`. Run `Bridge::clone(&self)` to
314/// access bridge from multiple threads. Ideally, run `Bridge::kill(&self)`
315/// before ending program to kill the screening process. However, it is not neccessary.
316pub struct Bridge {
317    bridge: std::sync::mpsc::Sender<(Cmds, usize, String)>,
318    hash: std::collections::HashSet<usize>,
319}
320
321impl Bridge {
322    /// Create a `Bridge` by passing an already created `Screen`. You won't be able to append children
323    /// to `Screen` once passed.
324    pub fn new(screen: Screen) -> Self {
325        let (tx, rx) = std::sync::mpsc::channel::<(Cmds, usize, String)>();
326        let mut hash: std::collections::HashSet<usize> = std::collections::HashSet::new();
327        let ids: Vec<usize> = screen
328            .windows
329            .iter()
330            .map(|window| match &window {
331                Some(window) => window.get_id(),
332                None => 0,
333            })
334            .rev()
335            .collect();
336        for id in ids {
337            hash.insert(id);
338        }
339        let screen = Box::new(screen);
340        std::thread::spawn(move || {
341            let mut screen = *screen;
342            for msg in rx.iter() {
343                match msg.0 {
344                    Cmds::Print => screen.print(msg.1, &msg.2).unwrap(),
345                    Cmds::Flush => screen.flush(msg.1).unwrap(),
346                    Cmds::Println => screen.println(msg.1, &msg.2).unwrap(),
347                    Cmds::Break => break,
348                };
349            }
350        });
351        Bridge { bridge: tx, hash }
352    }
353    /// Print a new `line` in window with `id`. Returns `()` if successful, `Err(std::io::ErrorKind)` if not.
354    pub fn println(&self, id: usize, msg: &str) -> Result<(), std::io::ErrorKind> {
355        self.validate_id(id)?;
356        self.bridge
357            .send((Cmds::Println, id, msg.to_string()))
358            .unwrap();
359        Ok(())
360    }
361    /// Print `line` in window with `id`, but do not flush. Returns `()` if successful, `Err(std::io::ErrorKind)` if not.
362    pub fn print(&self, id: usize, msg: &str) -> Result<(), std::io::ErrorKind> {
363        self.validate_id(id)?;
364        self.bridge
365            .send((Cmds::Print, id, msg.to_string()))
366            .unwrap();
367        Ok(())
368    }
369    /// Flush window with `id`. Returns `()` if successful, `Err(std::io::ErrorKind)` if not.
370    pub fn flush(&self, id: usize) -> Result<(), std::io::ErrorKind> {
371        self.validate_id(id)?;
372        self.bridge.send((Cmds::Flush, id, "".to_string())).unwrap();
373        Ok(())
374    }
375    fn validate_id(&self, id: usize) -> Result<(), std::io::ErrorKind> {
376        if self.hash.contains(&id) {
377            return Ok(());
378        }
379        Err(std::io::ErrorKind::NotFound)
380    }
381    /// Kills `Bridge`'s communication, and terminates the `Screen` screening process and deletes it.
382    pub fn kill(&self) {
383        self.bridge.send((Cmds::Break, 0, "".to_string())).unwrap();
384    }
385}
386
387impl std::clone::Clone for Bridge {
388    fn clone(&self) -> Self {
389        let bridge = self.bridge.clone();
390        let hash = self.hash.clone();
391        Bridge { bridge, hash }
392    }
393    fn clone_from(&mut self, source: &Self) {
394        *self = Bridge::clone(source);
395    }
396}