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