use crate::common::*;
use crate::window::{Window, WindowFactory, WindowRef};
use crate::{framebuffer::*, window::handler::WindowHandler};
use crossterm::*;
use std::backtrace::{Backtrace, BacktraceStatus};
use std::io::{self, BufWriter, Stdout, Write, stdout};
use std::time::Duration;
use vector2d::Vector2D;
pub struct Tui {
stdout: BufWriter<Stdout>,
framebuffer: Framebuffer,
window_handler: WindowHandler,
}
impl Tui {
pub fn new(main_window: WindowRef) -> Result<Self, io::Error> {
let mut out = BufWriter::new(stdout());
crossterm::terminal::enable_raw_mode()?;
out.execute(cursor::Hide)?;
out.execute(terminal::EnterAlternateScreen)?;
std::panic::set_hook(Box::new(|e| {
let mut out = stdout();
Self::reset_term(&mut out);
eprintln!("{e}");
let bt = Backtrace::capture();
if bt.status() == BacktraceStatus::Captured {
eprintln!("{bt}");
}
}));
let size = terminal::window_size().map(|s| Vector2D::new(s.columns, s.rows))?;
let window_handler = WindowHandler::new(main_window, size);
Ok(Self {
stdout: out,
framebuffer: Framebuffer::new(size),
window_handler,
})
}
pub fn new_default<W: Window + Default>() -> Result<Self, io::Error> {
Self::new(WindowFactory::create::<W>())
}
pub fn handle_events(&mut self) -> Result<(), io::Error> {
while event::poll(Duration::from_micros(16))? {
self.delegate_event(event::read()?);
}
Ok(())
}
pub fn handle_next_event(&mut self) -> Result<(), io::Error> {
self.delegate_event(event::read()?);
Ok(())
}
pub fn handle_next_event_poll(&mut self, timeout: Duration) -> Result<bool, io::Error> {
if event::poll(timeout)? {
self.delegate_event(event::read()?);
Ok(true)
}
else {
Ok(false)
}
}
pub fn render(&mut self) -> Result<(), io::Error> {
self.window_handler.render(&mut self.framebuffer);
let mut iter = self.framebuffer.get_line_iterator();
while let Some(next) = iter.next() {
if let LineContent::Line { index, glyphs } = next {
self.stdout.queue(cursor::MoveTo(0, index))?;
let (mut col_fg, mut col_bg) = (None, None);
for visual in glyphs.iter().filter(|v| !v.is_null()) {
if visual.style == Style::None {
Self::draw_visual(&mut self.stdout, visual, &mut col_fg, &mut col_bg)?;
}
else {
Style::apply_pre_styles(&mut self.stdout, visual.style)?;
Self::draw_visual(&mut self.stdout, visual, &mut col_fg, &mut col_bg)?;
Style::apply_post_styles(&mut self.stdout, visual.style)?;
}
}
}
}
self.stdout.flush()?;
Ok(())
}
fn delegate_event(&mut self, event: Event) {
match event {
Event::Resize(width, height) => {
let size = Vector2D::new(width, height);
self.window_handler.broadcast_resize(size);
self.framebuffer.resize(size);
}
e => self.window_handler.handle_event(e),
}
}
#[inline(always)]
fn draw_visual(
stdout: &mut BufWriter<Stdout>,
visual: &Glyph,
cur_fg: &mut Option<Color>,
cur_bg: &mut Option<Color>,
) -> Result<(), io::Error> {
if visual.fg != *cur_fg {
stdout.queue(style::SetForegroundColor(Color::Reset))?;
if let Some(fg) = visual.fg {
stdout.queue(style::SetForegroundColor(fg))?;
}
*cur_fg = visual.fg;
}
if visual.bg != *cur_bg {
stdout.queue(style::SetBackgroundColor(Color::Reset))?;
if let Some(bg) = visual.bg {
stdout.queue(style::SetBackgroundColor(bg))?;
}
*cur_bg = visual.bg;
}
stdout.queue(style::Print(&visual.grapheme))?;
Ok(())
}
fn reset_term(stdout: &mut Stdout) {
stdout.execute(terminal::LeaveAlternateScreen).ok();
stdout.execute(cursor::Show).ok();
}
}
impl Drop for Tui {
fn drop(&mut self) {
Self::reset_term(self.stdout.get_mut());
}
}