use std::io::{self, Write};
use crossterm::cursor::{Hide, Show};
use crossterm::event::{
DisableMouseCapture, EnableMouseCapture, Event as CtEvent, KeyCode as CtKeyCode, KeyEventKind,
KeyModifiers, MouseEventKind as CtMouseKind,
};
use crossterm::execute;
use crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
is_raw_mode_enabled,
};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Style;
use super::event::{Event, Key, KeyCode, Mouse, MouseKind};
use super::style::Theme;
use super::surface::Surface;
use super::view::{RenderCtx, View};
pub struct AltScreen {
active: bool,
}
impl AltScreen {
pub fn enter() -> io::Result<Self> {
let mut out = io::stdout();
execute!(out, EnterAlternateScreen, EnableMouseCapture)?;
out.flush()?;
Ok(Self { active: true })
}
pub fn leave(&mut self) {
if self.active {
let mut out = io::stdout();
let _ = execute!(out, DisableMouseCapture, LeaveAlternateScreen);
let _ = out.flush();
self.active = false;
}
}
}
impl Drop for AltScreen {
fn drop(&mut self) {
self.leave();
}
}
pub struct TerminalSession {
active: bool,
raw_mode_owned: bool,
}
impl TerminalSession {
pub fn enter() -> io::Result<Self> {
let raw_mode_owned = !is_raw_mode_enabled()?;
if raw_mode_owned {
enable_raw_mode()?;
}
let mut out = io::stdout();
if let Err(error) =
execute!(out, EnterAlternateScreen, EnableMouseCapture, Hide).and_then(|()| out.flush())
{
let _ = execute!(out, Show, DisableMouseCapture, LeaveAlternateScreen);
if raw_mode_owned {
let _ = disable_raw_mode();
}
return Err(error);
}
Ok(Self {
active: true,
raw_mode_owned,
})
}
pub fn leave(&mut self) {
if !self.active {
return;
}
let mut out = io::stdout();
let _ = execute!(out, Show, DisableMouseCapture, LeaveAlternateScreen);
let _ = out.flush();
if self.raw_mode_owned {
let _ = disable_raw_mode();
}
self.active = false;
}
}
impl Drop for TerminalSession {
fn drop(&mut self) {
self.leave();
}
}
pub struct Overlay<'a> {
pub area: Rect,
pub view: &'a dyn View,
pub clear: bool,
}
pub fn paint(
buffer: &mut Buffer,
area: Rect,
theme: &Theme,
root: &dyn View,
overlays: &[Overlay],
) {
{
let mut surface = Surface::new(buffer, area);
surface.fill(Style::default().bg(theme.background));
}
let ctx = RenderCtx::new(theme);
{
let mut surface = Surface::new(buffer, area);
root.render(area, &mut surface, &ctx);
}
for overlay in overlays {
let mut surface = Surface::new(buffer, overlay.area);
if overlay.clear {
surface.fill(Style::default().bg(theme.surface));
}
overlay
.view
.render(overlay.area, &mut surface, &ctx.with_focus(true));
}
}
pub fn translate_event(event: CtEvent) -> Option<Event> {
match event {
CtEvent::Key(k) => {
if k.kind == KeyEventKind::Release {
return None;
}
let code = match k.code {
CtKeyCode::Char(c) => KeyCode::Char(c),
CtKeyCode::Enter => KeyCode::Enter,
CtKeyCode::Esc => KeyCode::Esc,
CtKeyCode::Backspace => KeyCode::Backspace,
CtKeyCode::Delete => KeyCode::Delete,
CtKeyCode::Tab => KeyCode::Tab,
CtKeyCode::BackTab => KeyCode::BackTab,
CtKeyCode::Up => KeyCode::Up,
CtKeyCode::Down => KeyCode::Down,
CtKeyCode::Left => KeyCode::Left,
CtKeyCode::Right => KeyCode::Right,
CtKeyCode::Home => KeyCode::Home,
CtKeyCode::End => KeyCode::End,
CtKeyCode::PageUp => KeyCode::PageUp,
CtKeyCode::PageDown => KeyCode::PageDown,
_ => return None,
};
Some(Event::Key(Key {
code,
ctrl: k.modifiers.contains(KeyModifiers::CONTROL),
alt: k.modifiers.contains(KeyModifiers::ALT),
shift: k.modifiers.contains(KeyModifiers::SHIFT),
}))
}
CtEvent::Mouse(m) => {
let kind = match m.kind {
CtMouseKind::Down(_) => MouseKind::Down,
CtMouseKind::Up(_) => MouseKind::Up,
CtMouseKind::ScrollUp => MouseKind::ScrollUp,
CtMouseKind::ScrollDown => MouseKind::ScrollDown,
CtMouseKind::Moved | CtMouseKind::Drag(_) => MouseKind::Moved,
_ => return None,
};
Some(Event::Mouse(Mouse {
kind,
column: m.column,
row: m.row,
}))
}
CtEvent::Paste(text) => Some(Event::Paste(text)),
CtEvent::Resize(width, height) => Some(Event::Resize { width, height }),
_ => None,
}
}