1use retroglyph_core::backend::{Compositing, Cursor, CursorStyle, DrawCell, Input, Output};
4use retroglyph_core::event::Event;
5use retroglyph_core::grid::{HasSize, Pos, Size};
6use retroglyph_core::testing::InputRecording;
7use std::io;
8use std::path::Path;
9use std::sync::{Arc, Mutex};
10use std::time::{Duration, Instant};
11
12struct State {
14 recording: InputRecording,
15 active: bool,
20 last_event_at: Option<Instant>,
25}
26
27#[derive(Clone)]
34pub struct RecorderHandle {
35 state: Arc<Mutex<State>>,
36}
37
38impl RecorderHandle {
39 fn new(width: u16, height: u16) -> Self {
40 Self {
41 state: Arc::new(Mutex::new(State {
42 recording: InputRecording::new(width, height),
43 active: true,
44 last_event_at: None,
45 })),
46 }
47 }
48
49 fn record(&self, event: Event) {
52 let mut state = self.lock();
53 if !state.active {
54 return;
55 }
56 let now = Instant::now();
57 let delay = state
58 .last_event_at
59 .map_or(Duration::ZERO, |previous| now.duration_since(previous));
60 state.last_event_at = Some(now);
61 state.recording.push(delay, event);
62 }
63
64 fn lock(&self) -> std::sync::MutexGuard<'_, State> {
69 self.state
70 .lock()
71 .unwrap_or_else(std::sync::PoisonError::into_inner)
72 }
73
74 pub fn start(&self) {
78 let mut state = self.lock();
79 state.active = true;
80 state.last_event_at = None;
81 }
82
83 pub fn pause(&self) {
86 self.lock().active = false;
87 }
88
89 #[must_use]
91 pub fn snapshot(&self) -> InputRecording {
92 self.lock().recording.clone()
93 }
94
95 #[must_use]
97 pub fn stop(&self) -> InputRecording {
98 let mut state = self.lock();
99 state.active = false;
100 state.recording.clone()
101 }
102
103 pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
109 crate::rgrec::write(&self.snapshot(), path)
110 }
111}
112
113pub struct InputRecorder<B> {
140 inner: B,
141 handle: RecorderHandle,
142}
143
144impl<B: Output> InputRecorder<B> {
145 pub fn new(inner: B) -> Self {
147 let size = inner.size();
148 Self {
149 inner,
150 handle: RecorderHandle::new(size.width(), size.height()),
151 }
152 }
153}
154
155impl<B> InputRecorder<B> {
156 #[must_use]
159 pub fn handle(&self) -> RecorderHandle {
160 self.handle.clone()
161 }
162
163 pub fn start(&self) {
165 self.handle.start();
166 }
167
168 pub fn pause(&self) {
170 self.handle.pause();
171 }
172
173 #[must_use]
175 pub fn snapshot(&self) -> InputRecording {
176 self.handle.snapshot()
177 }
178
179 #[must_use]
181 pub fn stop(&self) -> InputRecording {
182 self.handle.stop()
183 }
184
185 pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
191 self.handle.save(path)
192 }
193
194 pub const fn inner(&self) -> &B {
196 &self.inner
197 }
198
199 pub const fn inner_mut(&mut self) -> &mut B {
201 &mut self.inner
202 }
203}
204
205impl<B: Output> Output for InputRecorder<B> {
206 type Error = B::Error;
207
208 fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
209 where
210 I: Iterator<Item = DrawCell<'a>>,
211 {
212 self.inner.draw(content)
213 }
214
215 fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
216 where
217 I: Iterator<Item = DrawCell<'a>>,
218 {
219 self.inner.draw_layers(content)
220 }
221
222 fn compositing(&self) -> Compositing {
223 self.inner.compositing()
224 }
225
226 fn flush(&mut self) -> Result<(), Self::Error> {
227 self.inner.flush()
228 }
229
230 fn size(&self) -> Size {
231 self.inner.size()
232 }
233
234 fn clear(&mut self) -> Result<(), Self::Error> {
235 self.inner.clear()
236 }
237
238 fn resize(&mut self, size: Size) {
239 self.inner.resize(size);
240 }
241}
242
243impl<B: Input> Input for InputRecorder<B> {
244 fn poll_event(&mut self, timeout: Duration) -> Option<Event> {
245 let event = self.inner.poll_event(timeout)?;
246 self.handle.record(event.clone());
247 Some(event)
248 }
249
250 fn push_event(&mut self, event: Event) {
251 self.inner.push_event(event);
252 }
253}
254
255impl<B: Cursor> Cursor for InputRecorder<B> {
256 fn set_cursor_visible(&mut self, visible: bool) {
257 self.inner.set_cursor_visible(visible);
258 }
259
260 fn set_cursor_position(&mut self, position: Pos) {
261 self.inner.set_cursor_position(position);
262 }
263
264 fn set_cursor_style(&mut self, style: CursorStyle) {
265 self.inner.set_cursor_style(style);
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use retroglyph_core::backend::Headless;
273 use retroglyph_core::event::{KeyCode, KeyEvent, KeyModifiers};
274
275 #[test]
276 fn records_events_seen_through_poll_event() {
277 let mut recorder = InputRecorder::new(Headless::new(10, 3));
278 recorder.push_event(Event::Key(KeyEvent::new(
279 KeyCode::Char('a'),
280 KeyModifiers::NONE,
281 )));
282 recorder.push_event(Event::Key(KeyEvent::new(
283 KeyCode::Char('b'),
284 KeyModifiers::NONE,
285 )));
286
287 assert!(recorder.poll_event(Duration::ZERO).is_some());
288 assert!(recorder.poll_event(Duration::ZERO).is_some());
289 assert!(recorder.poll_event(Duration::ZERO).is_none());
290
291 let recording = recorder.snapshot();
292 assert_eq!(recording.width(), 10);
293 assert_eq!(recording.height(), 3);
294 let events: Vec<_> = recording.events().map(|(_, event)| event.clone()).collect();
295 assert_eq!(
296 events,
297 vec![
298 Event::Key(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE)),
299 Event::Key(KeyEvent::new(KeyCode::Char('b'), KeyModifiers::NONE)),
300 ]
301 );
302 }
303
304 #[test]
305 fn pause_stops_recording_without_discarding_captured_events() {
306 let mut recorder = InputRecorder::new(Headless::new(4, 1));
307 recorder.push_event(Event::Key(KeyEvent::new(
308 KeyCode::Char('a'),
309 KeyModifiers::NONE,
310 )));
311 recorder.poll_event(Duration::ZERO);
312 assert_eq!(recorder.snapshot().len(), 1);
313
314 recorder.pause();
315 recorder.push_event(Event::Key(KeyEvent::new(
316 KeyCode::Char('b'),
317 KeyModifiers::NONE,
318 )));
319 recorder.poll_event(Duration::ZERO);
320 assert_eq!(
321 recorder.snapshot().len(),
322 1,
323 "paused recorder must not append new events"
324 );
325
326 recorder.start();
327 recorder.push_event(Event::Key(KeyEvent::new(
328 KeyCode::Char('c'),
329 KeyModifiers::NONE,
330 )));
331 recorder.poll_event(Duration::ZERO);
332 assert_eq!(
333 recorder.snapshot().len(),
334 2,
335 "resumed recorder must append new events again"
336 );
337 }
338
339 #[test]
340 fn handle_reaches_the_same_state_as_the_recorder() {
341 let mut recorder = InputRecorder::new(Headless::new(4, 1));
342 let handle = recorder.handle();
343 recorder.push_event(Event::Key(KeyEvent::new(
344 KeyCode::Char('a'),
345 KeyModifiers::NONE,
346 )));
347 recorder.poll_event(Duration::ZERO);
348
349 assert_eq!(handle.snapshot().len(), 1);
350 handle.pause();
351 recorder.push_event(Event::Key(KeyEvent::new(
352 KeyCode::Char('b'),
353 KeyModifiers::NONE,
354 )));
355 recorder.poll_event(Duration::ZERO);
356 assert_eq!(recorder.snapshot().len(), 1);
357 }
358
359 #[test]
360 fn stop_pauses_and_returns_the_captured_recording() {
361 let mut recorder = InputRecorder::new(Headless::new(4, 1));
362 recorder.push_event(Event::Key(KeyEvent::new(
363 KeyCode::Char('a'),
364 KeyModifiers::NONE,
365 )));
366 recorder.poll_event(Duration::ZERO);
367
368 let stopped = recorder.stop();
369 assert_eq!(stopped.len(), 1);
370
371 recorder.push_event(Event::Key(KeyEvent::new(
372 KeyCode::Char('b'),
373 KeyModifiers::NONE,
374 )));
375 recorder.poll_event(Duration::ZERO);
376 assert_eq!(recorder.snapshot().len(), 1, "stop() must pause recording");
377 }
378}