use std::io;
use scrin::{
Rect, Terminal, TerminalOptions,
core::buffer::Buffer,
widgets::{Block, List, ListState, Widget},
};
use crate::error::Result;
use crate::{normalize_bg, theme_surface, theme_white};
pub(crate) struct TerminalSession {
pub(crate) terminal: Terminal,
}
impl TerminalSession {
pub(crate) fn new() -> Result<Self> {
let mut terminal = Terminal::init_with(TerminalOptions {
mouse_capture: true,
..TerminalOptions::default()
})?;
terminal.clear();
Ok(Self { terminal })
}
}
impl Drop for TerminalSession {
fn drop(&mut self) {
let _ = self.terminal.restore();
}
}
pub(crate) struct RenderFrame<'a> {
area: Rect,
buffer: &'a mut Buffer,
cursor_position: Option<(u16, u16)>,
}
impl RenderFrame<'_> {
pub(crate) fn area(&self) -> Rect {
self.area
}
pub(crate) fn buffer(&mut self) -> &mut Buffer {
self.buffer
}
pub(crate) fn set_cursor_position(&mut self, x: u16, y: u16) {
self.cursor_position = Some((x, y));
}
pub(crate) fn render_widget<W>(&mut self, widget: W, area: Rect)
where
W: Widget,
{
widget.render(&mut *self.buffer, area);
}
}
pub(crate) fn draw_full_frame<F>(terminal: &mut Terminal, f: F) -> io::Result<()>
where
F: FnOnce(&mut RenderFrame<'_>),
{
terminal.size_changed()?;
terminal.clear();
let area = terminal.size();
let cursor_position = {
let mut frame = RenderFrame {
area,
buffer: terminal.back_buffer(),
cursor_position: None,
};
f(&mut frame);
frame.cursor_position
};
terminal.present_direct_timed()?;
if let Some((x, y)) = cursor_position {
terminal.set_cursor(x, y)?;
}
Ok(())
}
pub(crate) trait FrameRenderExt {
fn render_block_widget<'item, W: Widget>(&mut self, area: Rect, widget: W, block: Block<'item>);
fn render_block_stateful_list<'item>(
&mut self,
area: Rect,
list: &List<'item>,
state: &mut ListState,
block: Block<'item>,
);
}
impl FrameRenderExt for RenderFrame<'_> {
fn render_block_widget<'item, W: Widget>(
&mut self,
area: Rect,
widget: W,
block: Block<'item>,
) {
block.render(self.buffer(), area);
let inner = block.inner(area);
if inner.width > 0 && inner.height > 0 {
self.buffer()
.fill(inner, ' ', theme_white(), Some(theme_surface()));
widget.render(self.buffer(), inner);
normalize_bg(self.buffer(), inner, theme_surface());
}
}
fn render_block_stateful_list<'b>(
&mut self,
area: Rect,
list: &List<'b>,
state: &mut ListState,
block: Block<'b>,
) {
block.render(self.buffer(), area);
let inner = block.inner(area);
if inner.width > 0 && inner.height > 0 {
self.buffer()
.fill(inner, ' ', theme_white(), Some(theme_surface()));
list.render_stateful(self.buffer(), inner, state);
normalize_bg(self.buffer(), inner, theme_surface());
}
}
}