1use std::{
2 io::{self, Stdout, Write},
3 panic::{set_hook, take_hook},
4 time::Duration,
5};
6
7use crossterm::{
8 cursor::{self, Hide, Show},
9 event, execute, queue,
10 terminal::{self, *},
11 tty::IsTty,
12};
13
14pub use crate::prelude::*;
15
16#[derive(Default)]
17pub struct Inline {
18 active: bool,
19 kitty: bool,
20 start: u16,
21}
22
23impl AsMut<Buffer> for Window {
24 fn as_mut(&mut self) -> &mut Buffer {
25 self.buffer_mut()
26 }
27}
28
29pub struct Window {
47 io: io::Stdout,
48 buffers: [Buffer; 2],
49 active_buffer: usize,
50 events: Vec<Event>,
51
52 mouse_pos: Vec2,
54
55 inline: Option<Inline>,
57
58 just_resized: bool,
60}
61
62impl Default for Window {
63 fn default() -> Self {
64 Self::init().expect("Init should have succeeded")
65 }
66}
67
68impl Window {
69 pub fn new(io: io::Stdout) -> io::Result<Self> {
72 Ok(Self {
73 io,
74 buffers: [Buffer::new(size()?), Buffer::new(size()?)],
75 active_buffer: 0,
76 events: vec![],
77
78 mouse_pos: vec2(0, 0),
79
80 inline: None,
81
82 just_resized: false,
83 })
84 }
85
86 pub fn new_inline(io: io::Stdout, height: u16) -> io::Result<Self> {
88 let size = vec2(size()?.0, height);
89 Ok(Self {
90 io,
91 buffers: [Buffer::new(size), Buffer::new(size)],
92 active_buffer: 0,
93 events: vec![],
94
95 mouse_pos: vec2(0, 0),
96
97 inline: Some(Inline::default()),
98
99 just_resized: false,
100 })
101 }
102
103 pub fn init_inline(height: u16) -> io::Result<Self> {
106 let stdout = io::stdout();
107
108 assert!(stdout.is_tty());
109
110 Window::new_inline(stdout, height)
111 }
112
113 pub fn init() -> io::Result<Self> {
115 enable_raw_mode()?;
116
117 let mut stdout = io::stdout();
118
119 assert!(stdout.is_tty());
120
121 execute!(
122 stdout,
123 EnterAlternateScreen,
124 EnableMouseCapture,
125 EnableFocusChange,
126 Hide,
127 DisableLineWrap,
128 )?;
129
130 Window::new(stdout)
131 }
132
133 pub fn keyboard(&mut self) -> io::Result<()> {
135 if let Ok(t) = terminal::supports_keyboard_enhancement() {
136 if !t {
137 return Err(io::Error::new(
138 io::ErrorKind::Unsupported,
139 "Terminal doesn't support the kitty keyboard protocol",
140 ));
141 }
142 if let Some(inline) = &mut self.inline {
143 inline.kitty = true;
144 } else {
145 execute!(
146 self.io(),
147 PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::all())
148 )?;
149 }
150 Ok(())
151 } else {
152 Err(io::Error::new(
153 io::ErrorKind::Unsupported,
154 "Terminal doesn't support the kitty keyboard protocol",
155 ))
156 }
157 }
158
159 pub fn buffer(&self) -> &Buffer {
161 &self.buffers[self.active_buffer]
162 }
163
164 pub fn buffer_mut(&mut self) -> &mut Buffer {
166 &mut self.buffers[self.active_buffer]
167 }
168
169 pub fn swap_buffers(&mut self) {
171 self.active_buffer = 1 - self.active_buffer;
172 self.buffers[self.active_buffer].clear();
173 }
174
175 pub fn size(&self) -> Vec2 {
177 self.buffer().size()
178 }
179
180 pub fn restore(&mut self) -> io::Result<()> {
183 if terminal::supports_keyboard_enhancement().is_ok() {
184 queue!(self.io, PopKeyboardEnhancementFlags)?;
185 }
186 if let Some(inline) = &self.inline {
187 execute!(
188 self.io,
189 DisableMouseCapture,
190 DisableFocusChange,
191 PopKeyboardEnhancementFlags,
192 Show,
193 )?;
194
195 if terminal::size()?.1 != inline.start + 1 {
196 print!(
197 "{}",
198 "\n".repeat(self.buffers[self.active_buffer].size().y as usize)
199 );
200 }
201
202 disable_raw_mode()?;
203
204 Ok(())
205 } else {
206 execute!(
207 self.io,
208 PopKeyboardEnhancementFlags,
209 LeaveAlternateScreen,
210 DisableMouseCapture,
211 DisableFocusChange,
212 Show,
213 EnableLineWrap,
214 )?;
215
216 disable_raw_mode()
217 }
218 }
219
220 pub fn render(&mut self) -> io::Result<()> {
222 if self.inline.is_some() {
223 if !self.inline.as_ref().expect("Inline should be some").active {
224 print!("{}", "\n".repeat(self.buffer().size().y as usize));
226
227 enable_raw_mode()?;
228
229 execute!(
230 self.io,
231 EnableMouseCapture,
232 EnableFocusChange,
233 DisableLineWrap,
234 Hide,
235 )?;
236
237 if self.inline.as_ref().expect("Inline should be some").kitty {
238 execute!(
239 self.io,
240 PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::all())
241 )?;
242 }
243
244 let inline = self.inline.as_mut().expect("Inline should be some");
245
246 inline.active = true;
247 inline.start = cursor::position()?.1;
248 }
249
250 for (loc, cell) in
251 self.buffers[1 - self.active_buffer].diff(&self.buffers[self.active_buffer])
252 {
253 queue!(
254 self.io,
255 cursor::MoveTo(
256 loc.x,
257 self.inline.as_ref().expect("Inline should be some").start
258 - self.buffers[self.active_buffer].size().y
259 + loc.y
260 ),
261 Print(cell),
262 )?;
263 }
264
265 queue!(
266 self.io,
267 cursor::MoveTo(
268 0,
269 self.inline.as_ref().expect("Inline should be some").start
270 - self.buffers[self.active_buffer].size().y
271 )
272 )?;
273 } else {
274 if self.just_resized {
275 self.just_resized = false;
276 let cell = self.buffers[self.active_buffer].size();
277 for x in 0..cell.x {
278 for y in 0..cell.y {
279 let cell = self.buffers[self.active_buffer].get((x, y));
280 queue!(self.io, cursor::MoveTo(x, y), Print(cell))?;
281 }
282 }
283 }
284 for (loc, cell) in
285 self.buffers[1 - self.active_buffer].diff(&self.buffers[self.active_buffer])
286 {
287 queue!(self.io, cursor::MoveTo(loc.x, loc.y), Print(cell))?;
288 }
289 }
290 Ok(())
291 }
292
293 pub fn update(&mut self, poll: Duration) -> io::Result<()> {
295 let cursor_pos = cursor::position()?;
296
297 self.render()?;
299
300 self.swap_buffers();
301
302 queue!(self.io, cursor::MoveTo(cursor_pos.0, cursor_pos.1))?;
303
304 self.io.flush()?;
306
307 self.handle_event(poll)?;
309
310 Ok(())
311 }
312
313 pub fn handle_event(&mut self, poll: Duration) -> io::Result<()> {
315 self.events = vec![];
316
317 if event::poll(poll)? {
318 while event::poll(Duration::ZERO)? {
320 let event = event::read()?;
321
322 match event {
323 Event::Resize(width, height) => {
324 if self.inline.is_none() {
325 self.buffers =
326 [Buffer::new((width, height)), Buffer::new((width, height))];
327 self.just_resized = true;
328 }
329 }
330 Event::Mouse(MouseEvent { column, row, .. }) => {
331 self.mouse_pos = vec2(column, row)
332 }
333 _ => {}
334 }
335
336 self.events.push(event);
337 }
338 }
339
340 Ok(())
341 }
342
343 pub fn mouse_pos(&self) -> Vec2 {
344 self.mouse_pos
345 }
346
347 pub fn events(&self) -> &Vec<Event> {
349 &self.events
350 }
351
352 pub fn hover<V: Into<Vec2>>(&self, loc: V, size: V) -> io::Result<bool> {
354 let loc = loc.into();
355 let size = size.into();
356
357 let pos: Vec2 = self.mouse_pos();
358
359 Ok(pos.x <= loc.x + size.x && pos.x >= loc.x && pos.y <= loc.y + size.y && pos.y >= loc.y)
360 }
361
362 pub fn io(&mut self) -> &mut Stdout {
363 &mut self.io
364 }
365}
366
367#[macro_export]
380macro_rules! event {
381 ($window:expr, $event_type:pat => $($closure:tt)*) => {
382 $window.events().iter().any(|e| {
383 if let $event_type = e {
384 $($closure)*
385 } else {
386 false
387 }
388 })
389 };
390}
391
392pub fn handle_panics() {
394 let original_hook = take_hook();
395 set_hook(Box::new(move |e| {
396 Window::new(io::stdout())
397 .expect("Window should have created for panic")
398 .restore()
399 .expect("Window should have exited for panic");
400 original_hook(e);
401 }))
402}
403
404impl Drop for Window {
405 fn drop(&mut self) {
406 self.restore().expect("Restoration should have succeded");
407 }
408}