use std::{
io,
sync::Arc,
time::{
Duration,
Instant,
},
};
use futures::{
join,
lock::Mutex,
};
#[allow(unused_imports)]
use tracing::debug;
use tui::{
backend::Backend,
layout::{
Constraint,
Direction,
Layout,
},
Terminal,
};
use crate::widget::{
input::InputBuffer,
scrollback::ScrollbackBuffer,
};
pub struct Renderer<T: Backend> {
pub terminal: Terminal<T>,
input_handle: Arc<Mutex<InputBuffer>>,
scrollback_handle: Arc<Mutex<ScrollbackBuffer>>,
frame_duration: Duration,
last_refresh: Instant,
}
impl<T: Backend> Renderer<T> {
pub fn new(
mut backend: T,
input_handle: Arc<Mutex<InputBuffer>>,
scrollback_handle: Arc<Mutex<ScrollbackBuffer>>,
max_refresh: f64,
) -> Result<Self, io::Error> {
backend.clear()?;
let terminal = Terminal::new(backend)?;
Ok(Renderer {
terminal,
input_handle,
scrollback_handle,
last_refresh: Instant::now(),
frame_duration: Duration::from_secs_f64(1f64 / max_refresh),
})
}
pub async fn render(&mut self) -> Result<(), anyhow::Error> {
let terminal = &mut self.terminal;
let (input, scrollback) = join!(self.input_handle.lock(), self.scrollback_handle.lock());
let now = Instant::now();
if now - self.last_refresh < self.frame_duration {
return Ok(());
}
self.last_refresh = now;
terminal.draw(|f| {
let size = f.size();
let input_lines = input.wrapped(size.width as usize);
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints(
[
Constraint::Min(1),
Constraint::Length(input_lines.len() as u16),
]
.as_ref(),
)
.split(f.size());
let input_chunk = chunks[1];
let output_chunk = chunks[0];
let (cursor_x, cursor_y) = input_lines.cursor_pos();
f.render_widget(input_lines, input_chunk);
let scrollback = scrollback.widget();
f.render_widget(scrollback, output_chunk);
f.set_cursor(
input_chunk.x + cursor_x as u16,
input_chunk.y + cursor_y as u16,
);
})?;
Ok(())
}
}