1use std::io::{stdout, Write};
5
6use crossterm::{
7 cursor::{Hide, MoveTo, Show},
8 event::{
9 DisableMouseCapture, EnableMouseCapture,
10 Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, KeyboardEnhancementFlags,
11 ModifierKeyCode, MouseButton, MouseEvent, MouseEventKind,
12 PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
13 },
14 execute,
15 queue,
16 style::{
17 Attribute as CtAttribute,
18 Attributes as CtAttributes,
19 Color as CColor, Colors, Print, SetAttribute, SetBackgroundColor, SetColors, SetForegroundColor
20 },
21 terminal::{
22 disable_raw_mode, enable_raw_mode, Clear, EnterAlternateScreen, LeaveAlternateScreen
23 },
24 ExecutableCommand as _,
25};
26use dreg_core::prelude::*;
27
28
29
30pub mod prelude {
31 pub extern crate crossterm;
32 pub use crate::CrosstermPlatform;
33}
34
35
36
37pub struct CrosstermPlatform {
38 ctx: Context,
39
40 buffers: [Buffer; 2],
43 current: usize,
45
46 viewport_area: Rect,
47 last_known_size: Rect,
48}
49
50impl Platform for CrosstermPlatform {
51 fn run(mut self, mut program: impl Program) -> Result<()> {
52 bind_terminal()?;
53 while !program.should_exit() {
54 if crossterm::event::poll(std::time::Duration::from_millis(31))? {
55 let event = crossterm::event::read()?;
56 handle_crossterm_event(&mut self.ctx, event);
57 }
58
59 self.autoresize()?;
60
61 let size = self.size()?;
62 let frame = Frame {
63 context: &mut self.ctx,
64 area: size,
65 buffer: &mut self.buffers[self.current],
66 };
67
68 program.update(frame);
69 self.flush()?;
70 self.swap_buffers();
71 stdout().flush()?;
72 }
73 release_terminal()?;
74
75 Ok(())
76 }
77}
78
79impl CrosstermPlatform {
80 pub fn new() -> Result<Self> {
81 let (width, height) = crossterm::terminal::size()?;
82 let size = Rect::new(0, 0, width, height);
83
84 Ok(Self {
85 ctx: Context::default(),
86 buffers: [Buffer::empty(size), Buffer::empty(size)],
87 current: 0,
88 viewport_area: size,
89 last_known_size: size,
90 })
91 }
92
93 pub fn size(&self) -> std::io::Result<Rect> {
94 let (width, height) = crossterm::terminal::size()?;
95 Ok(Rect::new(0, 0, width, height))
96 }
97
98 fn resize(&mut self, size: Rect) -> std::io::Result<()> {
99 self.set_viewport_area(size);
100 execute!(stdout(), Clear(crossterm::terminal::ClearType::All))?;
101
102 self.last_known_size = size;
103 Ok(())
104 }
105
106 fn set_viewport_area(&mut self, area: Rect) {
107 self.buffers[self.current].resize(area);
108 self.buffers[1 - self.current].resize(area);
109 self.viewport_area = area;
110 }
111
112 fn autoresize(&mut self) -> std::io::Result<()> {
114 let size = self.size()?;
115 if size != self.last_known_size {
116 self.resize(size)?;
117 }
118 Ok(())
119 }
120
121 fn swap_buffers(&mut self) {
123 self.buffers[1 - self.current].reset();
124 self.current = 1 - self.current;
125 }
126
127 fn flush(&mut self) -> std::io::Result<()> {
128 let previous_buffer = &self.buffers[1 - self.current];
129 let current_buffer = &self.buffers[self.current];
130 let updates = previous_buffer.diff(current_buffer);
131 if let Some((col, row, _)) = updates.last() {
132 }
134 let content = updates.into_iter();
135
136 let mut writer = stdout();
137 let mut fg = Color::Reset;
138 let mut bg = Color::Reset;
139 #[cfg(feature = "underline-color")]
140 let mut underline_color = Color::Reset;
141 let mut modifier = Modifier::empty();
142 let mut last_pos: Option<(u16, u16)> = None;
143 for (x, y, cell) in content {
144 if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
146 queue!(writer, MoveTo(x, y))?;
147 }
148 last_pos = Some((x, y));
149 if cell.modifier != modifier {
150 let diff = ModifierDiff {
151 from: modifier,
152 to: cell.modifier,
153 };
154 diff.queue(&mut writer)?;
155 modifier = cell.modifier;
156 }
157 if cell.fg != fg || cell.bg != bg {
158 queue!(
159 writer,
160 SetColors(Colors::new(
161 dreg_color_to_crossterm_color(cell.fg),
162 dreg_color_to_crossterm_color(cell.bg),
163 ))
164 )?;
165 fg = cell.fg;
166 bg = cell.bg;
167 }
168 #[cfg(feature = "underline-color")]
169 if cell.underline_color != underline_color {
170 let color = dreg_color_to_crossterm_color(cell.underline_color);
171 queue!(writer, SetUnderlineColor(color))?;
172 underline_color = cell.underline_color;
173 }
174
175 queue!(writer, Print(cell.symbol()))?;
176 }
177
178 #[cfg(feature = "underline-color")]
179 return queue!(
180 writer,
181 SetForegroundColor(CColor::Reset),
182 SetBackgroundColor(CColor::Reset),
183 SetUnderlineColor(CColor::Reset),
184 SetAttribute(CtAttribute::Reset),
185 );
186 #[cfg(not(feature = "underline-color"))]
187 return queue!(
188 writer,
189 SetForegroundColor(CColor::Reset),
190 SetBackgroundColor(CColor::Reset),
191 SetAttribute(CtAttribute::Reset),
192 );
193 }
194}
195
196
197
198fn bind_terminal() -> Result<()> {
199 let mut writer = stdout();
200 enable_raw_mode()?;
201 writer.execute(EnableMouseCapture)?;
202 writer.execute(EnterAlternateScreen)?;
203 writer.execute(PushKeyboardEnhancementFlags(
204 KeyboardEnhancementFlags::REPORT_EVENT_TYPES
205 ))?;
206 writer.execute(Hide)?;
207 let original_hook = std::panic::take_hook();
208 std::panic::set_hook(Box::new(move |panic| {
209 release_terminal().unwrap();
210 original_hook(panic);
211 }));
212
213 Ok(())
214}
215
216fn release_terminal() -> Result<()> {
217 let mut writer = stdout();
218 disable_raw_mode()?;
219 writer.execute(DisableMouseCapture)?;
220 writer.execute(LeaveAlternateScreen)?;
221 writer.execute(PopKeyboardEnhancementFlags)?;
222 writer.execute(Show)?;
223
224 Ok(())
225}
226
227
228
229fn handle_crossterm_event(ctx: &mut Context, event: Event) {
230 match event {
231 Event::Key(KeyEvent { code, modifiers, kind, state }) => {
232 let mut scancodes = vec![];
233 if modifiers != KeyModifiers::NONE {
234 for m in modifiers.iter() {
235 match m {
236 KeyModifiers::SHIFT => scancodes.push(Scancode::L_SHIFT),
237 KeyModifiers::ALT => scancodes.push(Scancode::L_ALT),
238 KeyModifiers::CONTROL => scancodes.push(Scancode::L_CTRL),
239 _ => {} }
241 }
242 }
243 scancodes.extend(translate_keycode(code));
244 match kind {
245 KeyEventKind::Press => {
246 for scancode in scancodes {
247 ctx.handle_key_down(scancode);
248 }
249 }
250 KeyEventKind::Release => {
251 for scancode in scancodes {
252 ctx.handle_key_up(&scancode);
253 }
254 }
255 _ => {} }
257 }
258 Event::Mouse(MouseEvent { kind, column, row, .. }) => {
259 match kind {
260 MouseEventKind::Moved | MouseEventKind::Drag(_) => {
261 ctx.handle_input(Input::MouseMove(column, row));
262 }
263 MouseEventKind::Down(btn) => {
264 let code = match btn {
265 MouseButton::Left => Scancode::LMB,
266 MouseButton::Right => Scancode::RMB,
267 MouseButton::Middle => Scancode::MMB,
268 };
269 ctx.handle_key_down(code);
270 }
271 MouseEventKind::Up(btn) => {
272 let code = match btn {
273 MouseButton::Left => Scancode::LMB,
274 MouseButton::Right => Scancode::RMB,
275 MouseButton::Middle => Scancode::MMB,
276 };
277 ctx.handle_key_up(&code);
278 }
279 _ => {} }
281 }
282 Event::FocusGained => {
283 ctx.handle_input(Input::FocusChange(true));
284 }
285 Event::FocusLost => {
286 ctx.handle_input(Input::FocusChange(false));
287 }
288 Event::Resize(new_cols, new_rows) => {
289 ctx.handle_input(Input::Resize(new_cols, new_rows));
290 }
291 _ => {}
292 }
293}
294
295fn translate_keycode(code: KeyCode) -> Vec<Scancode> {
296 let mut scancodes = Vec::with_capacity(2);
298 match code {
299 KeyCode::Char(c) => {
300 let (modifier, scancode) = Scancode::from_char(c);
301 if let Some(mod_code) = modifier {
302 scancodes.push(mod_code);
303 }
304 scancodes.push(scancode);
305 }
306 KeyCode::F(n) => {
307 scancodes.push(match n {
308 1 => Scancode::F1,
309 2 => Scancode::F2,
310 3 => Scancode::F3,
311 4 => Scancode::F4,
312 5 => Scancode::F5,
313 6 => Scancode::F6,
314 7 => Scancode::F7,
315 8 => Scancode::F8,
316 9 => Scancode::F9,
317 10 => Scancode::F10,
318 _ => Scancode::NULL,
319 })
320 }
321 KeyCode::Modifier(mod_keycode) => match mod_keycode {
322 ModifierKeyCode::LeftShift => { scancodes.push(Scancode::L_SHIFT); },
323 ModifierKeyCode::LeftAlt => { scancodes.push(Scancode::L_ALT); },
324 ModifierKeyCode::LeftControl => { scancodes.push(Scancode::L_CTRL); },
325
326 ModifierKeyCode::RightShift => { scancodes.push(Scancode::R_SHIFT); },
327 ModifierKeyCode::RightAlt => { scancodes.push(Scancode::R_ALT); },
328 ModifierKeyCode::RightControl => { scancodes.push(Scancode::R_CTRL); },
329
330 _ => {} }
332
333 KeyCode::Esc => { scancodes.push(Scancode::ESC); },
334 KeyCode::Backspace => { scancodes.push(Scancode::BACKSPACE); }
335 KeyCode::Tab => { scancodes.push(Scancode::TAB); },
336 KeyCode::BackTab => { scancodes.extend([Scancode::L_SHIFT, Scancode::TAB]); }
337 KeyCode::Enter => { scancodes.push(Scancode::ENTER); },
338 KeyCode::Delete => { scancodes.push(Scancode::DELETE); },
339 KeyCode::Insert => { scancodes.push(Scancode::INSERT); },
340 KeyCode::CapsLock => { scancodes.push(Scancode::CAPSLOCK); },
341
342 KeyCode::Left => { scancodes.push(Scancode::LEFT); },
343 KeyCode::Right => { scancodes.push(Scancode::RIGHT); },
344 KeyCode::Up => { scancodes.push(Scancode::UP); },
345 KeyCode::Down => { scancodes.push(Scancode::DOWN); },
346
347 KeyCode::Home => { scancodes.push(Scancode::HOME); },
348 KeyCode::End => { scancodes.push(Scancode::END); },
349 KeyCode::PageUp => { scancodes.push(Scancode::PAGEDOWN); },
350 KeyCode::PageDown => { scancodes.push(Scancode::PAGEUP); },
351
352 _ => {}
353 }
354
355 scancodes
356}
357
358
359
360struct ModifierDiff {
364 pub from: Modifier,
365 pub to: Modifier,
366}
367
368impl ModifierDiff {
369 fn queue<W: std::io::Write>(self, mut w: W) -> std::io::Result<()> {
370 let removed = self.from - self.to;
372 if removed.contains(Modifier::REVERSED) {
373 queue!(w, SetAttribute(CtAttribute::NoReverse))?;
374 }
375 if removed.contains(Modifier::BOLD) {
376 queue!(w, SetAttribute(CtAttribute::NormalIntensity))?;
377 if self.to.contains(Modifier::DIM) {
378 queue!(w, SetAttribute(CtAttribute::Dim))?;
379 }
380 }
381 if removed.contains(Modifier::ITALIC) {
382 queue!(w, SetAttribute(CtAttribute::NoItalic))?;
383 }
384 if removed.contains(Modifier::UNDERLINED) {
385 queue!(w, SetAttribute(CtAttribute::NoUnderline))?;
386 }
387 if removed.contains(Modifier::DIM) {
388 queue!(w, SetAttribute(CtAttribute::NormalIntensity))?;
389 }
390 if removed.contains(Modifier::CROSSED_OUT) {
391 queue!(w, SetAttribute(CtAttribute::NotCrossedOut))?;
392 }
393 if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
394 queue!(w, SetAttribute(CtAttribute::NoBlink))?;
395 }
396
397 let added = self.to - self.from;
398 if added.contains(Modifier::REVERSED) {
399 queue!(w, SetAttribute(CtAttribute::Reverse))?;
400 }
401 if added.contains(Modifier::BOLD) {
402 queue!(w, SetAttribute(CtAttribute::Bold))?;
403 }
404 if added.contains(Modifier::ITALIC) {
405 queue!(w, SetAttribute(CtAttribute::Italic))?;
406 }
407 if added.contains(Modifier::UNDERLINED) {
408 queue!(w, SetAttribute(CtAttribute::Underlined))?;
409 }
410 if added.contains(Modifier::DIM) {
411 queue!(w, SetAttribute(CtAttribute::Dim))?;
412 }
413 if added.contains(Modifier::CROSSED_OUT) {
414 queue!(w, SetAttribute(CtAttribute::CrossedOut))?;
415 }
416 if added.contains(Modifier::SLOW_BLINK) {
417 queue!(w, SetAttribute(CtAttribute::SlowBlink))?;
418 }
419 if added.contains(Modifier::RAPID_BLINK) {
420 queue!(w, SetAttribute(CtAttribute::RapidBlink))?;
421 }
422
423 Ok(())
424 }
425}
426
427fn translate_attribute(value: CtAttribute) -> Modifier {
428 translate_attributes(CtAttributes::from(value))
432}
433
434fn translate_attributes(value: CtAttributes) -> Modifier {
435 let mut res = Modifier::empty();
436
437 if value.has(CtAttribute::Bold) {
438 res |= Modifier::BOLD;
439 }
440 if value.has(CtAttribute::Dim) {
441 res |= Modifier::DIM;
442 }
443 if value.has(CtAttribute::Italic) {
444 res |= Modifier::ITALIC;
445 }
446 if value.has(CtAttribute::Underlined)
447 || value.has(CtAttribute::DoubleUnderlined)
448 || value.has(CtAttribute::Undercurled)
449 || value.has(CtAttribute::Underdotted)
450 || value.has(CtAttribute::Underdashed)
451 {
452 res |= Modifier::UNDERLINED;
453 }
454 if value.has(CtAttribute::SlowBlink) {
455 res |= Modifier::SLOW_BLINK;
456 }
457 if value.has(CtAttribute::RapidBlink) {
458 res |= Modifier::RAPID_BLINK;
459 }
460 if value.has(CtAttribute::Reverse) {
461 res |= Modifier::REVERSED;
462 }
463 if value.has(CtAttribute::Hidden) {
464 res |= Modifier::HIDDEN;
465 }
466 if value.has(CtAttribute::CrossedOut) {
467 res |= Modifier::CROSSED_OUT;
468 }
469
470 res
471}
472
473
474
475fn dreg_color_to_crossterm_color(color: Color) -> CColor {
476 match color {
477 Color::Reset => CColor::Reset,
478 Color::Black => CColor::Black,
479 Color::Red => CColor::DarkRed,
480 Color::Green => CColor::DarkGreen,
481 Color::Yellow => CColor::DarkYellow,
482 Color::Blue => CColor::DarkBlue,
483 Color::Magenta => CColor::DarkMagenta,
484 Color::Cyan => CColor::DarkCyan,
485 Color::Gray => CColor::Grey,
486 Color::DarkGray => CColor::DarkGrey,
487 Color::LightRed => CColor::Red,
488 Color::LightGreen => CColor::Green,
489 Color::LightBlue => CColor::Blue,
490 Color::LightYellow => CColor::Yellow,
491 Color::LightMagenta => CColor::Magenta,
492 Color::LightCyan => CColor::Cyan,
493 Color::White => CColor::White,
494 Color::Indexed(i) => CColor::AnsiValue(i),
495 Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
496 }
497}
498
499fn crossterm_color_to_dreg_color(value: CColor) -> Color {
500 match value {
501 CColor::Reset => Color::Reset,
502 CColor::Black => Color::Black,
503 CColor::DarkRed => Color::Red,
504 CColor::DarkGreen => Color::Green,
505 CColor::DarkYellow => Color::Yellow,
506 CColor::DarkBlue => Color::Blue,
507 CColor::DarkMagenta => Color::Magenta,
508 CColor::DarkCyan => Color::Cyan,
509 CColor::Grey => Color::Gray,
510 CColor::DarkGrey => Color::DarkGray,
511 CColor::Red => Color::LightRed,
512 CColor::Green => Color::LightGreen,
513 CColor::Blue => Color::LightBlue,
514 CColor::Yellow => Color::LightYellow,
515 CColor::Magenta => Color::LightMagenta,
516 CColor::Cyan => Color::LightCyan,
517 CColor::White => Color::White,
518 CColor::Rgb { r, g, b } => Color::Rgb(r, g, b),
519 CColor::AnsiValue(v) => Color::Indexed(v),
520 }
521}