use crate::core::draw::Cell;
use crate::core::event::Event;
use crate::core::geometry::{Point, Rect};
use crate::core::palette::Attr;
use std::time::Duration;
pub struct MockTerminal {
width: u16,
height: u16,
buffer: Vec<Vec<Cell>>,
cursor_pos: Point,
cursor_visible: bool,
events: Vec<Event>,
}
impl MockTerminal {
pub fn new(width: u16, height: u16) -> Self {
use crate::core::palette::{Attr, TvColor};
let default_attr = Attr::new(TvColor::LightGray, TvColor::Black);
let default_cell = Cell::new(' ', default_attr);
let buffer = vec![vec![default_cell; width as usize]; height as usize];
Self {
width,
height,
buffer,
cursor_pos: Point::zero(),
cursor_visible: false,
events: Vec::new(),
}
}
pub fn size(&self) -> (u16, u16) {
(self.width, self.height)
}
pub fn put_char(&mut self, x: i16, y: i16, ch: char) {
if self.in_bounds(x, y) {
self.buffer[y as usize][x as usize].ch = ch;
}
}
pub fn put_char_with_attr(&mut self, x: i16, y: i16, ch: char, attr: Attr) {
if self.in_bounds(x, y) {
self.buffer[y as usize][x as usize] = Cell::new(ch, attr);
}
}
pub fn get_char(&self, x: i16, y: i16) -> Option<char> {
if self.in_bounds(x, y) {
Some(self.buffer[y as usize][x as usize].ch)
} else {
None
}
}
pub fn get_cell(&self, x: i16, y: i16) -> Option<Cell> {
if self.in_bounds(x, y) {
Some(self.buffer[y as usize][x as usize])
} else {
None
}
}
fn in_bounds(&self, x: i16, y: i16) -> bool {
x >= 0 && y >= 0 && x < self.width as i16 && y < self.height as i16
}
pub fn set_cursor(&mut self, x: i16, y: i16) {
self.cursor_pos = Point::new(x, y);
}
pub fn cursor_pos(&self) -> Point {
self.cursor_pos
}
pub fn show_cursor(&mut self, visible: bool) {
self.cursor_visible = visible;
}
pub fn is_cursor_visible(&self) -> bool {
self.cursor_visible
}
pub fn push_event(&mut self, event: Event) {
self.events.push(event);
}
pub fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
if self.events.is_empty() {
None
} else {
Some(self.events.remove(0))
}
}
pub fn clear_events(&mut self) {
self.events.clear();
}
pub fn fill_rect(&mut self, rect: Rect, ch: char, attr: Attr) {
for y in rect.a.y..rect.b.y {
for x in rect.a.x..rect.b.x {
self.put_char_with_attr(x, y, ch, attr);
}
}
}
pub fn get_row(&self, y: i16) -> Option<String> {
if y >= 0 && y < self.height as i16 {
Some(
self.buffer[y as usize]
.iter()
.map(|cell| cell.ch)
.collect()
)
} else {
None
}
}
pub fn get_rect_text(&self, rect: Rect) -> Vec<String> {
let mut result = Vec::new();
for y in rect.a.y..rect.b.y {
if y >= 0 && y < self.height as i16 {
let row: String = (rect.a.x..rect.b.x)
.filter_map(|x| {
if x >= 0 && x < self.width as i16 {
Some(self.buffer[y as usize][x as usize].ch)
} else {
None
}
})
.collect();
result.push(row);
}
}
result
}
pub fn clear(&mut self) {
use crate::core::palette::{Attr, TvColor};
let default_attr = Attr::new(TvColor::LightGray, TvColor::Black);
let default_cell = Cell::new(' ', default_attr);
for row in &mut self.buffer {
for cell in row {
*cell = default_cell;
}
}
}
}
#[cfg(test)]
mod send_assertions {
use crate::core::geometry::{Point, Rect};
use crate::core::event::Event;
use crate::core::palette::{Attr, TvColor};
use crate::core::draw::Cell;
fn assert_send<T: Send>() {}
#[test]
#[allow(dead_code)]
fn test_core_types_are_send() {
assert_send::<Point>();
assert_send::<Rect>();
assert_send::<Event>();
assert_send::<Attr>();
assert_send::<TvColor>();
assert_send::<Cell>();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::palette::{Attr, TvColor};
#[test]
fn test_mock_terminal_basic() {
let mut terminal = MockTerminal::new(80, 25);
assert_eq!(terminal.size(), (80, 25));
terminal.put_char(5, 10, 'X');
assert_eq!(terminal.get_char(5, 10), Some('X'));
}
#[test]
fn test_mock_terminal_attributes() {
let mut terminal = MockTerminal::new(80, 25);
let attr = Attr::new(TvColor::White, TvColor::Blue);
terminal.put_char_with_attr(0, 0, 'A', attr);
let cell = terminal.get_cell(0, 0).unwrap();
assert_eq!(cell.ch, 'A');
assert_eq!(cell.attr, attr);
}
#[test]
fn test_mock_terminal_bounds() {
let terminal = MockTerminal::new(10, 10);
assert_eq!(terminal.get_char(0, 0), Some(' '));
assert_eq!(terminal.get_char(9, 9), Some(' '));
assert_eq!(terminal.get_char(10, 10), None);
assert_eq!(terminal.get_char(-1, -1), None);
}
#[test]
fn test_mock_terminal_cursor() {
let mut terminal = MockTerminal::new(80, 25);
terminal.set_cursor(10, 5);
assert_eq!(terminal.cursor_pos(), Point::new(10, 5));
terminal.show_cursor(true);
assert!(terminal.is_cursor_visible());
terminal.show_cursor(false);
assert!(!terminal.is_cursor_visible());
}
#[test]
fn test_mock_terminal_events() {
let mut terminal = MockTerminal::new(80, 25);
let event1 = Event::keyboard(0x1B);
let event2 = Event::keyboard(0x0D);
terminal.push_event(event1);
terminal.push_event(event2);
assert_eq!(terminal.poll_event(Duration::from_millis(0)).unwrap().key_code, 0x1B);
assert_eq!(terminal.poll_event(Duration::from_millis(0)).unwrap().key_code, 0x0D);
assert!(terminal.poll_event(Duration::from_millis(0)).is_none());
}
#[test]
fn test_mock_terminal_get_row() {
let mut terminal = MockTerminal::new(10, 5);
for (i, ch) in "Hello".chars().enumerate() {
terminal.put_char(i as i16, 0, ch);
}
let row = terminal.get_row(0).unwrap();
assert!(row.starts_with("Hello"));
}
#[test]
fn test_mock_terminal_fill_rect() {
let mut terminal = MockTerminal::new(20, 10);
let attr = Attr::new(TvColor::White, TvColor::Blue);
terminal.fill_rect(Rect::new(5, 5, 10, 8), 'X', attr);
assert_eq!(terminal.get_char(5, 5), Some('X'));
assert_eq!(terminal.get_char(9, 7), Some('X'));
assert_eq!(terminal.get_char(4, 5), Some(' '));
assert_eq!(terminal.get_char(10, 8), Some(' '));
}
}