use crate::backend::{Cursor, Input, Output};
use crate::event::Event;
use crate::grid::{Grid, Pos, Size};
use alloc::collections::VecDeque;
use alloc::string::String;
use core::time::Duration;
pub struct Headless {
grid: Grid,
cursor_visible: bool,
cursor_pos: Pos,
event_queue: VecDeque<Event>,
}
impl Headless {
#[must_use]
pub fn new(width: u16, height: u16) -> Self {
Self {
grid: Grid::new(width, height),
cursor_visible: false,
cursor_pos: Pos::default(),
event_queue: VecDeque::new(),
}
}
#[must_use]
pub const fn grid(&self) -> &Grid {
&self.grid
}
#[must_use]
pub const fn cursor_visible(&self) -> bool {
self.cursor_visible
}
#[must_use]
pub const fn cursor_position(&self) -> Pos {
self.cursor_pos
}
pub fn push_event(&mut self, event: Event) {
self.event_queue.push_back(event);
}
#[must_use]
pub fn format_view(&self) -> String {
let mut out = String::new();
for y in 0..self.grid.height() {
for x in 0..self.grid.width() {
let cell = &self.grid[Pos::new(x, y)];
#[cfg(feature = "egc")]
let is_spacer = cell
.flags()
.contains(crate::tile::TileFlags::WIDE_CHAR_SPACER);
#[cfg(not(feature = "egc"))]
let is_spacer = cell.glyph() == '\0';
let c = if is_spacer {
' '
} else if cell.glyph() == ' ' {
'·'
} else {
cell.glyph()
};
out.push(c);
}
out.push('\n');
}
out
}
}
impl Output for Headless {
type Error = core::convert::Infallible;
fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
where
I: Iterator<Item = crate::backend::DrawCell<'a>>,
{
for cell in content {
let pos = cell.pos;
self.grid.put_tile(0, pos, *cell.tile);
let extra = crate::grid::TileExtra {
grapheme: cell.grapheme.map(alloc::sync::Arc::from),
tint: cell.tint,
};
self.grid.set_extra(0, pos.x, pos.y, extra);
}
Ok(())
}
fn resize(&mut self, size: Size) {
self.grid.resize(size.width(), size.height());
}
fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn size(&self) -> Size {
Size::new(self.grid.width(), self.grid.height())
}
fn clear(&mut self) -> Result<(), Self::Error> {
self.grid.clear_all();
Ok(())
}
}
impl Input for Headless {
fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
self.event_queue.pop_front()
}
fn push_event(&mut self, event: Event) {
Self::push_event(self, event);
}
}
impl Cursor for Headless {
fn set_cursor_visible(&mut self, visible: bool) {
self.cursor_visible = visible;
}
fn set_cursor_position(&mut self, position: Pos) {
self.cursor_pos = position;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_headless_new() {
let backend = Headless::new(80, 25);
assert_eq!(backend.grid().width(), 80);
assert_eq!(backend.grid().height(), 25);
}
#[test]
fn test_headless_events() {
let mut backend = Headless::new(10, 10);
let event = Event::Close;
backend.push_event(event);
assert_eq!(backend.poll_event(Duration::ZERO), Some(Event::Close));
assert_eq!(backend.poll_event(Duration::ZERO), None);
}
#[test]
fn test_format_view_snapshot() {
use crate::Terminal;
let backend = Headless::new(10, 3);
let mut term = Terminal::new(backend);
term.draw(|s| {
s.put((1, 1), 'H', crate::style::Style::default());
s.put((2, 1), 'i', crate::style::Style::default());
})
.expect("draw failed");
let view = term.backend().format_view();
insta::assert_snapshot!(view, @r#"
··········
·Hi·······
··········
"#);
}
#[test]
fn test_format_view_renders_span_fallback_glyphs() {
use crate::Terminal;
let backend = Headless::new(6, 3);
let mut term = Terminal::new(backend);
term.draw(|s| {
s.put_span((1, 0), &["C=", "[]"], crate::style::Style::default())
.expect("span write");
})
.expect("draw failed");
let view = term.backend().format_view();
insta::assert_snapshot!(view, @r#"
·C=···
·[]···
······
"#);
}
}