use crate::{
backend::Backend,
buffer::Buffer,
layout::Rect,
widgets::{StatefulWidget, Widget},
};
use std::io;
#[derive(Debug, Clone, PartialEq)]
enum ResizeBehavior {
Fixed,
Auto,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Viewport {
area: Rect,
resize_behavior: ResizeBehavior,
}
impl Viewport {
pub fn fixed(area: Rect) -> Viewport {
Viewport {
area,
resize_behavior: ResizeBehavior::Fixed,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TerminalOptions {
pub viewport: Viewport,
}
#[derive(Debug)]
pub struct Terminal<B>
where
B: Backend,
{
backend: B,
buffers: [Buffer; 2],
current: usize,
hidden_cursor: bool,
viewport: Viewport,
}
pub struct Frame<'a, B: 'a>
where
B: Backend,
{
terminal: &'a mut Terminal<B>,
cursor_position: Option<(u16, u16)>,
}
impl<'a, B> Frame<'a, B>
where
B: Backend,
{
pub fn size(&self) -> Rect {
self.terminal.viewport.area
}
pub fn render_widget<W>(&mut self, widget: W, area: Rect)
where
W: Widget,
{
widget.render(area, self.terminal.current_buffer_mut());
}
pub fn render_stateful_widget<W>(&mut self, widget: W, area: Rect, state: &mut W::State)
where
W: StatefulWidget,
{
widget.render(area, self.terminal.current_buffer_mut(), state);
}
pub fn set_cursor(&mut self, x: u16, y: u16) {
self.cursor_position = Some((x, y));
}
}
pub struct CompletedFrame<'a> {
pub buffer: &'a Buffer,
pub area: Rect,
}
impl<B> Drop for Terminal<B>
where
B: Backend,
{
fn drop(&mut self) {
if self.hidden_cursor {
if let Err(err) = self.show_cursor() {
eprintln!("Failed to show the cursor: {}", err);
}
}
}
}
impl<B> Terminal<B>
where
B: Backend,
{
pub fn new(backend: B) -> io::Result<Terminal<B>> {
let size = backend.size()?;
Terminal::with_options(
backend,
TerminalOptions {
viewport: Viewport {
area: size,
resize_behavior: ResizeBehavior::Auto,
},
},
)
}
pub fn with_options(backend: B, options: TerminalOptions) -> io::Result<Terminal<B>> {
Ok(Terminal {
backend,
buffers: [
Buffer::empty(options.viewport.area),
Buffer::empty(options.viewport.area),
],
current: 0,
hidden_cursor: false,
viewport: options.viewport,
})
}
pub fn get_frame(&mut self) -> Frame<B> {
Frame {
terminal: self,
cursor_position: None,
}
}
pub fn current_buffer_mut(&mut self) -> &mut Buffer {
&mut self.buffers[self.current]
}
pub fn backend(&self) -> &B {
&self.backend
}
pub fn backend_mut(&mut self) -> &mut B {
&mut self.backend
}
pub fn flush(&mut self) -> io::Result<()> {
let previous_buffer = &self.buffers[1 - self.current];
let current_buffer = &self.buffers[self.current];
let updates = previous_buffer.diff(current_buffer);
self.backend.draw(updates.into_iter())
}
pub fn resize(&mut self, area: Rect) -> io::Result<()> {
self.buffers[self.current].resize(area);
self.buffers[1 - self.current].resize(area);
self.viewport.area = area;
self.clear()
}
pub fn autoresize(&mut self) -> io::Result<()> {
if self.viewport.resize_behavior == ResizeBehavior::Auto {
let size = self.size()?;
if size != self.viewport.area {
self.resize(size)?;
}
};
Ok(())
}
pub fn draw<F>(&mut self, f: F) -> io::Result<CompletedFrame>
where
F: FnOnce(&mut Frame<B>),
{
self.autoresize()?;
let mut frame = self.get_frame();
f(&mut frame);
let cursor_position = frame.cursor_position;
self.flush()?;
match cursor_position {
None => self.hide_cursor()?,
Some((x, y)) => {
self.show_cursor()?;
self.set_cursor(x, y)?;
}
}
self.buffers[1 - self.current].reset();
self.current = 1 - self.current;
self.backend.flush()?;
Ok(CompletedFrame {
buffer: &self.buffers[1 - self.current],
area: self.viewport.area,
})
}
pub fn hide_cursor(&mut self) -> io::Result<()> {
self.backend.hide_cursor()?;
self.hidden_cursor = true;
Ok(())
}
pub fn show_cursor(&mut self) -> io::Result<()> {
self.backend.show_cursor()?;
self.hidden_cursor = false;
Ok(())
}
pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
self.backend.get_cursor()
}
pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
self.backend.set_cursor(x, y)
}
pub fn clear(&mut self) -> io::Result<()> {
self.backend.clear()?;
self.buffers[1 - self.current].reset();
Ok(())
}
pub fn size(&self) -> io::Result<Rect> {
self.backend.size()
}
}