retroglyph/backend/
headless.rs1use crate::backend::Backend;
5use crate::event::Event;
6use crate::grid::{Grid, Pos, Size};
7use crate::tile::Tile;
8use alloc::collections::VecDeque;
9use alloc::string::String;
10use core::time::Duration;
11
12pub struct Headless {
15 grid: Grid,
16 cursor_visible: bool,
17 cursor_pos: Pos,
18 event_queue: VecDeque<Event>,
19}
20
21impl Headless {
22 #[must_use]
24 pub fn new(width: u16, height: u16) -> Self {
25 Self {
26 grid: Grid::new(width, height),
27 cursor_visible: false,
28 cursor_pos: Pos::default(),
29 event_queue: VecDeque::new(),
30 }
31 }
32
33 #[must_use]
35 pub const fn grid(&self) -> &Grid {
36 &self.grid
37 }
38
39 #[must_use]
41 pub const fn cursor_visible(&self) -> bool {
42 self.cursor_visible
43 }
44
45 #[must_use]
47 pub const fn cursor_position(&self) -> Pos {
48 self.cursor_pos
49 }
50
51 pub fn push_event(&mut self, event: Event) {
53 self.event_queue.push_back(event);
54 }
55
56 #[must_use]
60 pub fn format_view(&self) -> String {
61 let mut out = String::new();
62 for y in 0..self.grid.height() {
63 for x in 0..self.grid.width() {
64 let cell = self.grid.get(x, y);
65 #[cfg(feature = "egc")]
66 let is_spacer = cell
67 .flags()
68 .contains(crate::tile::TileFlags::WIDE_CHAR_SPACER);
69 #[cfg(not(feature = "egc"))]
70 let is_spacer = cell.glyph() == '\0';
71 let c = if is_spacer {
72 ' '
73 } else if cell.glyph() == ' ' {
74 '·'
75 } else {
76 cell.glyph()
77 };
78 out.push(c);
79 }
80 out.push('\n');
81 }
82 out
83 }
84}
85
86impl Backend for Headless {
87 fn draw<'a, I>(&mut self, content: I)
88 where
89 I: Iterator<Item = (Pos, &'a Tile)>,
90 {
91 for (pos, cell) in content {
92 self.grid.checked_put(pos.x, pos.y, cell.clone());
93 }
94 }
95
96 fn resize(&mut self, size: Size) {
97 self.grid.resize(size.width, size.height);
98 }
99
100 fn flush(&mut self) {
101 }
103
104 fn size(&self) -> Size {
105 Size {
106 width: self.grid.width(),
107 height: self.grid.height(),
108 }
109 }
110
111 fn clear(&mut self) {
112 self.grid.clear_all();
113 }
114
115 fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
116 self.event_queue.pop_front()
117 }
118
119 fn push_event(&mut self, event: Event) {
120 Self::push_event(self, event);
121 }
122
123 fn set_cursor_visible(&mut self, visible: bool) {
124 self.cursor_visible = visible;
125 }
126
127 fn set_cursor_position(&mut self, position: Pos) {
128 self.cursor_pos = position;
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_headless_new() {
138 let backend = Headless::new(80, 25);
139 assert_eq!(backend.grid().width(), 80);
140 assert_eq!(backend.grid().height(), 25);
141 }
142
143 #[test]
144 fn test_headless_events() {
145 let mut backend = Headless::new(10, 10);
146 let event = Event::Close;
147 backend.push_event(event);
148 assert_eq!(backend.poll_event(Duration::ZERO), Some(Event::Close));
149 assert_eq!(backend.poll_event(Duration::ZERO), None);
150 }
151
152 #[test]
153 fn test_format_view_snapshot() {
154 use crate::Terminal;
155 let backend = Headless::new(10, 3);
156 let mut term = Terminal::new(backend);
157 term.put(1, 1, 'H');
158 term.put(2, 1, 'i');
159 term.present();
160 let view = term.backend().format_view();
161 insta::assert_snapshot!(view, @r#"
162 ··········
163 ·Hi·······
164 ··········
165 "#);
166 }
167}