ratatui_kit/terminal/
cross_terminal.rs1use super::TerminalImpl;
2use crossterm::{
3 cursor,
4 event::{self, EventStream, KeyboardEnhancementFlags},
5 execute, queue, terminal,
6};
7use futures::{StreamExt, stream::BoxStream};
8use ratatui::Frame;
9use std::io::{self, IsTerminal, stdout};
10fn set_panic_hook() {
11 let hook = std::panic::take_hook();
12 std::panic::set_hook(Box::new(move |info| {
13 terminal::disable_raw_mode().unwrap();
14 execute!(
15 stdout(),
16 terminal::LeaveAlternateScreen,
17 event::DisableMouseCapture,
18 event::PopKeyboardEnhancementFlags,
19 cursor::Show
20 )
21 .unwrap();
22 ratatui::restore();
23 hook(info);
24 }));
25}
26
27pub struct CrossTerminal {
36 input_is_terminal: bool,
37 dest: std::io::Stdout,
38 raw_mode_enabled: bool,
39 enabled_keyboard_enhancement: bool,
40 fullscreen: bool,
41 terminal: ratatui::Terminal<ratatui::backend::CrosstermBackend<std::io::Stdout>>,
42}
43
44impl CrossTerminal {
45 pub fn new(fullscreen: bool) -> io::Result<Self> {
48 let mut dest = io::stdout();
49 queue!(dest, cursor::Hide)?;
51
52 if fullscreen {
54 queue!(dest, terminal::EnterAlternateScreen)?;
55 }
56
57 set_panic_hook();
59
60 Ok(Self {
61 input_is_terminal: io::stdin().is_terminal(),
62 raw_mode_enabled: false,
63 enabled_keyboard_enhancement: false,
64 fullscreen,
65 terminal: ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(stdout()))?,
66 dest,
67 })
68 }
69
70 pub fn set_raw_mode_enabled(&mut self, enabled: bool) -> io::Result<()> {
73 if enabled != self.raw_mode_enabled {
74 if enabled {
75 if terminal::supports_keyboard_enhancement().unwrap_or(false) {
77 execute!(
78 self.dest,
79 event::PushKeyboardEnhancementFlags(
80 KeyboardEnhancementFlags::REPORT_EVENT_TYPES
81 )
82 )?;
83 self.enabled_keyboard_enhancement = true;
84 }
85 if self.fullscreen {
87 execute!(self.dest, event::EnableMouseCapture)?;
88 }
89
90 terminal::enable_raw_mode()?;
92 } else {
93 terminal::disable_raw_mode()?;
95 if self.enabled_keyboard_enhancement {
97 execute!(self.dest, event::PopKeyboardEnhancementFlags)?;
98 self.enabled_keyboard_enhancement = false;
99 }
100 if self.fullscreen {
102 execute!(self.dest, event::DisableMouseCapture)?;
103 }
104 }
105
106 self.raw_mode_enabled = enabled;
107 }
108
109 Ok(())
110 }
111}
112
113impl Drop for CrossTerminal {
116 fn drop(&mut self) {
118 let _ = self.set_raw_mode_enabled(false);
119 if self.fullscreen {
120 let _ = queue!(self.dest, terminal::LeaveAlternateScreen);
121 }
122 let _ = execute!(self.dest, cursor::Show);
123 }
124}
125
126impl TerminalImpl for CrossTerminal {
129 type Event = event::Event;
130
131 fn is_raw_mode_enabled(&self) -> bool {
133 self.raw_mode_enabled
134 }
135
136 fn event_stream(&mut self) -> io::Result<BoxStream<'static, Self::Event>> {
138 if !self.input_is_terminal {
139 return Ok(futures::stream::pending().boxed());
140 }
141
142 self.set_raw_mode_enabled(true)?;
144
145 Ok(EventStream::new()
147 .filter_map(|event| async move { event.ok() })
148 .boxed())
149 }
150
151 fn received_ctrl_c(event: Self::Event) -> bool {
153 matches!(
154 event,
155 event::Event::Key(event::KeyEvent {
156 code: event::KeyCode::Char('c'),
157 modifiers: event::KeyModifiers::CONTROL,
158 kind: event::KeyEventKind::Press,
159 ..
160 })
161 )
162 }
163
164 fn draw<F>(&mut self, f: F) -> io::Result<()>
165 where
166 F: FnOnce(&mut Frame),
167 {
168 self.terminal.draw(f)?;
169 Ok(())
170 }
171}