use std::io;
use ratatui::backend::{Backend, ClearType, WindowSize};
use crate::error;
use ratatui::buffer::Cell;
use ratatui::layout::{Position, Size};
use crate::backend::CaptureBackend;
pub struct DualBackend<P: Backend> {
primary: P,
capture: CaptureBackend,
sync_sizes: bool,
}
impl<P: Backend> DualBackend<P> {
pub fn new(primary: P, capture: CaptureBackend) -> Self {
Self {
primary,
capture,
sync_sizes: true,
}
}
pub fn with_auto_capture(primary: P) -> error::Result<Self> {
let size = primary.size()?;
let capture = CaptureBackend::new(size.width, size.height);
Ok(Self {
primary,
capture,
sync_sizes: true,
})
}
pub fn with_history(primary: P, capture: CaptureBackend, sync_sizes: bool) -> Self {
Self {
primary,
capture,
sync_sizes,
}
}
pub fn disable_sync_sizes(mut self) -> Self {
self.sync_sizes = false;
self
}
pub fn primary(&self) -> &P {
&self.primary
}
pub fn primary_mut(&mut self) -> &mut P {
&mut self.primary
}
pub fn capture(&self) -> &CaptureBackend {
&self.capture
}
pub fn capture_mut(&mut self) -> &mut CaptureBackend {
&mut self.capture
}
pub fn into_inner(self) -> (P, CaptureBackend) {
(self.primary, self.capture)
}
pub fn captured_text(&self) -> String {
self.capture.to_string()
}
pub fn captured_ansi(&self) -> String {
self.capture.to_ansi()
}
pub fn contains_text(&self, needle: &str) -> bool {
self.capture.contains_text(needle)
}
pub fn frame_count(&self) -> u64 {
self.capture.current_frame()
}
}
impl<P: Backend> Backend for DualBackend<P> {
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
where
I: Iterator<Item = (u16, u16, &'a Cell)>,
{
let cells: Vec<_> = content.collect();
self.primary
.draw(cells.iter().map(|&(x, y, c)| (x, y, c)))?;
self.capture.draw(cells.into_iter())?;
Ok(())
}
fn hide_cursor(&mut self) -> io::Result<()> {
self.primary.hide_cursor()?;
self.capture.hide_cursor()?;
Ok(())
}
fn show_cursor(&mut self) -> io::Result<()> {
self.primary.show_cursor()?;
self.capture.show_cursor()?;
Ok(())
}
fn get_cursor_position(&mut self) -> io::Result<Position> {
self.primary.get_cursor_position()
}
fn set_cursor_position<Pos: Into<Position>>(&mut self, position: Pos) -> io::Result<()> {
let pos = position.into();
self.primary.set_cursor_position(pos)?;
self.capture.set_cursor_position(pos)?;
Ok(())
}
fn clear(&mut self) -> io::Result<()> {
self.primary.clear()?;
self.capture.clear()?;
Ok(())
}
fn clear_region(&mut self, clear_type: ClearType) -> io::Result<()> {
self.primary.clear_region(clear_type)?;
self.capture.clear_region(clear_type)?;
Ok(())
}
fn size(&self) -> io::Result<Size> {
self.primary.size()
}
fn window_size(&mut self) -> io::Result<WindowSize> {
self.primary.window_size()
}
fn flush(&mut self) -> io::Result<()> {
self.primary.flush()?;
self.capture.flush()?;
Ok(())
}
}
pub struct DualBackendBuilder<P: Backend> {
primary: P,
width: Option<u16>,
height: Option<u16>,
history_capacity: usize,
sync_sizes: bool,
}
impl<P: Backend> DualBackendBuilder<P> {
pub fn new(primary: P) -> Self {
Self {
primary,
width: None,
height: None,
history_capacity: 0,
sync_sizes: true,
}
}
pub fn capture_size(mut self, width: u16, height: u16) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
pub fn with_history(mut self, capacity: usize) -> Self {
self.history_capacity = capacity;
self
}
pub fn no_sync_sizes(mut self) -> Self {
self.sync_sizes = false;
self
}
pub fn build(self) -> error::Result<DualBackend<P>> {
let size = self.primary.size()?;
let width = self.width.unwrap_or(size.width);
let height = self.height.unwrap_or(size.height);
let capture = if self.history_capacity > 0 {
CaptureBackend::with_history(width, height, self.history_capacity)
} else {
CaptureBackend::new(width, height)
};
Ok(DualBackend {
primary: self.primary,
capture,
sync_sizes: self.sync_sizes,
})
}
}
#[cfg(test)]
mod tests;