use std::io::{self, Write};
use crossterm::{
cursor::{Hide, MoveTo, Show},
event::{DisableMouseCapture, EnableMouseCapture},
execute, queue,
style::{Attribute, Print, SetAttribute, SetBackgroundColor, SetForegroundColor},
terminal::{
disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen,
LeaveAlternateScreen,
},
};
use super::{buffer::Buffer, cell::Style, cursor::Cursor};
pub struct Backend {
current: Buffer,
previous: Buffer,
cursor: Cursor,
alternate_screen: bool,
mouse_capture: bool,
width: u16,
height: u16,
}
impl Backend {
pub fn new() -> io::Result<Self> {
let (width, height) = crossterm::terminal::size()?;
Ok(Self {
current: Buffer::new(width, height),
previous: Buffer::new(width, height),
cursor: Cursor::new(),
alternate_screen: false,
mouse_capture: false,
width,
height,
})
}
pub fn init(&mut self) -> io::Result<()> {
enable_raw_mode()?;
execute!(io::stdout(), EnterAlternateScreen, Hide)?;
self.alternate_screen = true;
Ok(())
}
pub fn init_with_mouse(&mut self) -> io::Result<()> {
self.init()?;
execute!(io::stdout(), EnableMouseCapture)?;
self.mouse_capture = true;
Ok(())
}
pub fn restore(&mut self) -> io::Result<()> {
let mut stdout = io::stdout();
if self.mouse_capture {
execute!(stdout, DisableMouseCapture)?;
self.mouse_capture = false;
}
if self.alternate_screen {
execute!(stdout, LeaveAlternateScreen, Show)?;
self.alternate_screen = false;
}
disable_raw_mode()?;
Ok(())
}
#[inline]
pub fn width(&self) -> u16 {
self.width
}
#[inline]
pub fn height(&self) -> u16 {
self.height
}
#[inline]
pub fn buffer_mut(&mut self) -> &mut Buffer {
&mut self.current
}
#[inline]
pub fn buffer(&self) -> &Buffer {
&self.current
}
#[inline]
pub fn cursor_mut(&mut self) -> &mut Cursor {
&mut self.cursor
}
#[inline]
pub fn cursor(&self) -> &Cursor {
&self.cursor
}
pub fn sync_size(&mut self) -> io::Result<bool> {
let (width, height) = crossterm::terminal::size()?;
if width != self.width || height != self.height {
self.width = width;
self.height = height;
self.current.resize(width, height);
self.previous.resize(width, height);
Ok(true)
} else {
Ok(false)
}
}
pub fn clear(&mut self) -> io::Result<()> {
self.current.clear();
self.previous.clear();
execute!(io::stdout(), Clear(ClearType::All))?;
Ok(())
}
pub fn flush(&mut self) -> io::Result<()> {
let mut stdout = io::stdout();
let mut last_style = Style::new();
let mut last_x: i32 = -1;
let mut last_y: i32 = -1;
let changes: Vec<_> = self.current.diff(&self.previous).collect();
for (x, y, cell) in changes {
if cell.is_continuation {
continue;
}
if x as i32 != last_x + 1 || y as i32 != last_y {
queue!(stdout, MoveTo(x, y))?;
}
if cell.style != last_style {
self.apply_style(&mut stdout, &cell.style, &last_style)?;
last_style = cell.style;
}
queue!(stdout, Print(&cell.symbol))?;
last_x = x as i32;
last_y = y as i32;
}
queue!(
stdout,
SetForegroundColor(crossterm::style::Color::Reset),
SetBackgroundColor(crossterm::style::Color::Reset),
SetAttribute(Attribute::Reset)
)?;
if self.cursor.visible {
let cursor_style = if self.cursor.blinking {
self.cursor.shape.to_blinking_cursor_style()
} else {
self.cursor.shape.to_cursor_style()
};
queue!(
stdout,
MoveTo(self.cursor.x, self.cursor.y),
cursor_style,
Show
)?;
} else {
queue!(stdout, Hide)?;
}
stdout.flush()?;
std::mem::swap(&mut self.current, &mut self.previous);
self.current.clear();
Ok(())
}
fn apply_style<W: Write>(&self, writer: &mut W, new: &Style, old: &Style) -> io::Result<()> {
if new.fg != old.fg {
if let Some(fg) = new.fg {
queue!(writer, SetForegroundColor(fg.into()))?;
} else {
queue!(writer, SetForegroundColor(crossterm::style::Color::Reset))?;
}
}
if new.bg != old.bg {
if let Some(bg) = new.bg {
queue!(writer, SetBackgroundColor(bg.into()))?;
} else {
queue!(writer, SetBackgroundColor(crossterm::style::Color::Reset))?;
}
}
if new.bold != old.bold {
queue!(
writer,
SetAttribute(if new.bold {
Attribute::Bold
} else {
Attribute::NormalIntensity
})
)?;
}
if new.dim != old.dim {
queue!(
writer,
SetAttribute(if new.dim {
Attribute::Dim
} else {
Attribute::NormalIntensity
})
)?;
}
if new.italic != old.italic {
queue!(
writer,
SetAttribute(if new.italic {
Attribute::Italic
} else {
Attribute::NoItalic
})
)?;
}
if new.underline != old.underline {
queue!(
writer,
SetAttribute(if new.underline {
Attribute::Underlined
} else {
Attribute::NoUnderline
})
)?;
}
if new.blink != old.blink {
queue!(
writer,
SetAttribute(if new.blink {
Attribute::SlowBlink
} else {
Attribute::NoBlink
})
)?;
}
if new.strikethrough != old.strikethrough {
queue!(
writer,
SetAttribute(if new.strikethrough {
Attribute::CrossedOut
} else {
Attribute::NotCrossedOut
})
)?;
}
if new.reverse != old.reverse {
queue!(
writer,
SetAttribute(if new.reverse {
Attribute::Reverse
} else {
Attribute::NoReverse
})
)?;
}
if new.hidden != old.hidden {
queue!(
writer,
SetAttribute(if new.hidden {
Attribute::Hidden
} else {
Attribute::NoHidden
})
)?;
}
Ok(())
}
}
impl Default for Backend {
fn default() -> Self {
Self::new().expect("Failed to create backend")
}
}
impl Drop for Backend {
fn drop(&mut self) {
let _ = self.restore();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backend_size() {
if let Ok(backend) = Backend::new() {
assert!(backend.width() > 0);
assert!(backend.height() > 0);
}
}
}