Skip to main content

fm/app/
displayer.rs

1use std::io::Stdout;
2use std::sync::mpsc::{self, TryRecvError};
3use std::sync::Arc;
4use std::thread;
5use std::time::Duration;
6
7use anyhow::Result;
8use parking_lot::Mutex;
9use ratatui::backend::CrosstermBackend;
10use ratatui::Terminal;
11
12use crate::app::Status;
13use crate::io::Display;
14use crate::log_info;
15
16/// Is responsible for running the display thread.
17/// Rendering is done at 30 fps if possible.
18/// It holds a transmitter used to ask the thread to stop and an handle to this thread.
19/// It's usefull to ensure the terminal is reset properly which should always be the case.
20pub struct Displayer {
21    tx: mpsc::Sender<()>,
22    handle: thread::JoinHandle<Result<()>>,
23}
24
25impl Displayer {
26    const THIRTY_PER_SECONDS_IN_MILLIS: u64 = 33;
27
28    pub fn new(term: Terminal<CrosstermBackend<Stdout>>, status: Arc<Mutex<Status>>) -> Self {
29        let (tx, rx) = mpsc::channel();
30        let mut display = Display::new(term);
31
32        let handle = thread::spawn(move || -> Result<()> {
33            loop {
34                match rx.try_recv() {
35                    Ok(_) | Err(TryRecvError::Disconnected) => {
36                        log_info!("terminating displayer");
37                        display.restore_terminal()?;
38                        drop(display);
39                        break;
40                    }
41                    Err(TryRecvError::Empty) => {}
42                }
43                let mut status = status.lock();
44                if !status.internal_settings.is_disabled() {
45                    let frame = display.display_all(&status);
46                    if status.wants_buffer() {
47                        if let Ok(frame) = frame {
48                            status.set_buffer(frame.buffer.clone())
49                        }
50                    }
51                }
52                if status.should_tabs_images_be_cleared() {
53                    status.set_tabs_images_cleared();
54                }
55                if status.should_be_cleared() {
56                    status.internal_settings.reset_clear()
57                }
58                drop(status);
59
60                thread::sleep(Duration::from_millis(Self::THIRTY_PER_SECONDS_IN_MILLIS));
61            }
62            Ok(())
63        });
64        Self { tx, handle }
65    }
66
67    pub fn quit(self) {
68        log_info!("stopping display loop");
69        match self.tx.send(()) {
70            Ok(()) => (),
71            Err(e) => log_info!("Displayer::quit error {e:?}"),
72        };
73        let _ = self.handle.join();
74    }
75}