1pub use crate::prelude::*;
2
3use crossterm::{
4 cursor::{self, Hide, MoveTo, Show},
5 event, execute, queue,
6 terminal::{self, *},
7 tty::IsTty,
8};
9use std::{
10 io::{self, Stdout, Write},
11 panic::{set_hook, take_hook},
12 time::Duration,
13};
14
15#[derive(Default)]
16pub struct Inline {
17 active: bool,
18 kitty: bool,
19 start: u16,
20}
21
22impl AsMut<Buffer> for Window {
23 fn as_mut(&mut self) -> &mut Buffer {
24 self.buffer_mut()
25 }
26}
27
28pub struct Window {
42 io: io::Stdout,
43 buffers: [Buffer; 2],
44 active_buffer: usize,
45 events: Vec<Event>,
46
47 last_cursor: (bool, Vec2, SetCursorStyle),
48
49 cursor_visible: bool,
50 cursor: Vec2,
51 cursor_style: SetCursorStyle,
52 cursor_dirty: bool,
53
54 mouse_pos: Vec2,
56 inline: Option<Inline>,
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: [
75 Buffer::new_filled(size()?, ' '),
76 Buffer::new_filled(size()?, ' '),
77 ],
78 active_buffer: 0,
79 events: vec![],
80 last_cursor: (false, vec2(0, 0), SetCursorStyle::SteadyBlock),
81 cursor_visible: false,
82 cursor_style: SetCursorStyle::SteadyBlock,
83 cursor: vec2(0, 0),
84 cursor_dirty: false,
85 mouse_pos: vec2(0, 0),
86 inline: None,
87 just_resized: false,
88 })
89 }
90
91 pub fn new_inline(io: io::Stdout, height: u16) -> io::Result<Self> {
93 let size = vec2(size()?.0, height);
94 Ok(Self {
95 io,
96 buffers: [Buffer::new_filled(size, ' '), Buffer::new_filled(size, ' ')],
97 active_buffer: 0,
98 events: vec![],
99 last_cursor: (false, vec2(0, 0), SetCursorStyle::SteadyBlock),
100 cursor_visible: false,
101 cursor_style: SetCursorStyle::SteadyBlock,
102 cursor: vec2(0, 0),
103 cursor_dirty: false,
104 mouse_pos: vec2(0, 0),
105 inline: Some(Inline::default()),
106 just_resized: false,
107 })
108 }
109
110 pub fn init_inline(height: u16) -> io::Result<Self> {
113 let stdout = io::stdout();
114 assert!(stdout.is_tty());
115 Window::new_inline(stdout, height)
116 }
117
118 pub fn init() -> io::Result<Self> {
120 enable_raw_mode()?;
121 let mut stdout = io::stdout();
122 assert!(stdout.is_tty());
123 execute!(
124 stdout,
125 EnterAlternateScreen,
126 EnableMouseCapture,
127 EnableFocusChange,
128 EnableBracketedPaste,
129 Hide,
130 DisableLineWrap,
131 )?;
132 Window::new(stdout)
133 }
134
135 pub fn keyboard(&mut self) -> io::Result<()> {
137 if let Ok(t) = terminal::supports_keyboard_enhancement() {
138 if !t {
139 return Err(io::Error::new(
140 io::ErrorKind::Unsupported,
141 "Terminal doesn't support the kitty keyboard protocol",
142 ));
143 }
144 if let Some(inline) = &mut self.inline {
145 inline.kitty = true;
146 } else {
147 execute!(
148 self.io(),
149 PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::all())
150 )?;
151 }
152 Ok(())
153 } else {
154 Err(io::Error::new(
155 io::ErrorKind::Unsupported,
156 "Terminal doesn't support the kitty keyboard protocol",
157 ))
158 }
159 }
160
161 pub fn buffer(&self) -> &Buffer {
163 &self.buffers[self.active_buffer]
164 }
165
166 pub fn buffer_mut(&mut self) -> &mut Buffer {
168 &mut self.buffers[self.active_buffer]
169 }
170
171 pub fn swap_buffers(&mut self) {
173 self.active_buffer = 1 - self.active_buffer;
174 self.buffers[self.active_buffer].fill(' ');
175 }
176
177 pub fn size(&self) -> Vec2 {
179 self.buffer().size()
180 }
181
182 pub fn restore(&mut self) -> io::Result<()> {
185 if terminal::supports_keyboard_enhancement().is_ok() {
186 queue!(self.io, PopKeyboardEnhancementFlags)?;
187 }
188 if let Some(inline) = &self.inline {
189 execute!(
190 self.io,
191 DisableMouseCapture,
192 DisableFocusChange,
193 DisableBracketedPaste,
194 PopKeyboardEnhancementFlags,
195 Show,
196 )?;
197 if terminal::size()?.1 != inline.start + 1 {
198 print!(
199 "{}",
200 "\n".repeat(self.buffers[self.active_buffer].size().y as usize)
201 );
202 }
203 disable_raw_mode()?;
204 Ok(())
205 } else {
206 execute!(
207 self.io,
208 PopKeyboardEnhancementFlags,
209 LeaveAlternateScreen,
210 DisableMouseCapture,
211 DisableFocusChange,
212 DisableBracketedPaste,
213 Show,
214 EnableLineWrap,
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 EnableBracketedPaste,
234 DisableLineWrap,
235 Hide,
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 let inline = self.inline.as_mut().expect("Inline should be some");
244 inline.active = true;
245 inline.start = cursor::position()?.1;
246 }
247
248 for (loc, cell) in
249 self.buffers[1 - self.active_buffer].diff(&self.buffers[self.active_buffer])
250 {
251 queue!(
252 self.io,
253 cursor::MoveTo(
254 loc.x,
255 self.inline.as_ref().expect("Inline should be some").start
256 - self.buffers[self.active_buffer].size().y
257 + loc.y
258 ),
259 Print(cell),
260 )?;
261
262 self.cursor_dirty = true;
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]
280 .get((x, y))
281 .expect("Cell should be in bounds");
282 queue!(self.io, cursor::MoveTo(x, y), Print(cell))?;
283
284 self.cursor_dirty = true;
285 }
286 }
287 }
288
289 for (loc, cell) in
290 self.buffers[1 - self.active_buffer].diff(&self.buffers[self.active_buffer])
291 {
292 queue!(self.io, cursor::MoveTo(loc.x, loc.y), Print(cell))?;
293
294 self.cursor_dirty = true;
295 }
296 }
297 Ok(())
298 }
299
300 pub fn update(&mut self, poll: Duration) -> io::Result<()> {
302 self.render()?;
304 self.swap_buffers();
305 self.render_cursor()?;
306 self.io.flush()?;
308 self.handle_event(poll)?;
310 Ok(())
311 }
312
313 pub fn render_cursor(&mut self) -> io::Result<()> {
314 if self.cursor_style != self.last_cursor.2
316 || self.cursor != self.last_cursor.1
317 || self.cursor_visible != self.last_cursor.0
318 || self.cursor_dirty
319 {
320 self.cursor_dirty = false;
321 if self.cursor_visible {
322 let cursor = self.cursor;
323 let style = self.cursor_style;
324
325 let actual_pos = if let Some(inline) = &self.inline {
327 vec2(
328 cursor.x,
329 inline.start - self.buffers[self.active_buffer].size().y + cursor.y,
330 )
331 } else {
332 cursor
333 };
334
335 queue!(self.io(), MoveTo(actual_pos.x, actual_pos.y), style, Show)?;
336 } else {
337 queue!(self.io(), Hide)?;
338 }
339 }
340 self.last_cursor = (self.cursor_visible, self.cursor, self.cursor_style);
341 Ok(())
342 }
343
344 pub fn handle_event(&mut self, poll: Duration) -> io::Result<()> {
346 self.events = vec![];
347 if event::poll(poll)? {
348 while event::poll(Duration::ZERO)? {
350 let event = event::read()?;
351 match event {
352 Event::Resize(width, height) => {
353 if self.inline.is_none() {
354 self.buffers = [
355 Buffer::new_filled((width, height), ' '),
356 Buffer::new_filled((width, height), ' '),
357 ];
358 self.just_resized = true;
359 }
360 }
361 Event::Mouse(MouseEvent { column, row, .. }) => {
362 self.mouse_pos = vec2(column, row)
363 }
364 _ => {}
365 }
366 self.events.push(event);
367 }
368 }
369 Ok(())
370 }
371
372 pub fn cursor_visible(&self) -> bool {
374 self.cursor_visible
375 }
376
377 pub fn cursor(&self) -> Vec2 {
379 self.cursor
380 }
381
382 pub fn cursor_style(&self) -> SetCursorStyle {
384 self.cursor_style
385 }
386
387 pub fn set_cursor_visible(&mut self, visible: bool) {
389 self.cursor_visible = visible;
390 }
391
392 pub fn set_cursor(&mut self, pos: Vec2) {
394 let size = self.size();
395 self.cursor.x = pos.x.min(size.x.saturating_sub(1));
396 self.cursor.y = pos.y.min(size.y.saturating_sub(1));
397 }
398
399 pub fn set_cursor_style(&mut self, style: SetCursorStyle) {
401 self.cursor_style = style;
402 }
403
404 pub fn move_cursor(&mut self, x: i16, y: i16) {
406 let size = self.size();
407 self.cursor.x = self
408 .cursor
409 .x
410 .saturating_add_signed(x)
411 .min(size.x.saturating_sub(1));
412 self.cursor.y = self
413 .cursor
414 .y
415 .saturating_add_signed(y)
416 .min(size.y.saturating_sub(1));
417 }
418
419 pub fn mouse_pos(&self) -> Vec2 {
420 self.mouse_pos
421 }
422
423 pub fn insert_event(&mut self, event: Event) {
427 match event {
428 Event::Resize(width, height) => {
429 if self.inline.is_none() {
430 self.buffers = [
431 Buffer::new_filled((width, height), ' '),
432 Buffer::new_filled((width, height), ' '),
433 ];
434 self.just_resized = true;
435 }
436 }
437 Event::Mouse(MouseEvent { column, row, .. }) => self.mouse_pos = vec2(column, row),
438 _ => {}
439 }
440
441 self.events.push(event);
442 }
443
444 pub fn clear_events(&mut self) {
447 self.events.clear();
448 }
449
450 pub fn events(&self) -> &Vec<Event> {
452 &self.events
453 }
454
455 pub fn hover<V: Into<Vec2>>(&self, loc: V, size: V) -> io::Result<bool> {
457 let loc = loc.into();
458 let size = size.into();
459 let pos: Vec2 = self.mouse_pos();
460 Ok(pos.x <= loc.x + size.x && pos.x >= loc.x && pos.y <= loc.y + size.y && pos.y >= loc.y)
461 }
462
463 pub fn io(&mut self) -> &mut Stdout {
464 &mut self.io
465 }
466}
467
468#[macro_export]
486macro_rules! event {
487 ($window:expr, $event_type:pat => $($closure:tt)*) => {
488 $window.events().iter().any(|e| {
489 if let $event_type = e {
490 $($closure)*
491 } else {
492 false
493 }
494 })
495 };
496}
497
498pub fn handle_panics() {
500 let original_hook = take_hook();
501 set_hook(Box::new(move |e| {
502 Window::new(io::stdout())
503 .expect("Window should have created for panic")
504 .restore()
505 .expect("Window should have exited for panic");
506 original_hook(e);
507 }))
508}
509
510impl Drop for Window {
511 fn drop(&mut self) {
512 self.restore().expect("Restoration should have succeded");
513 }
514}