use std::collections::VecDeque;
use super::super::Backend;
use super::super::Surface;
use super::command::Command;
use crate::backend::utils::AnsiFlags;
use crate::backend::utils::AnsiFormatter;
use crate::graphics::Color;
use crate::graphics::Point;
use crate::graphics::Size;
use crate::input::KeyModifier;
use crate::prelude::CharFlags;
use crate::system::Error;
use crate::system::SystemEvent;
use crate::system::{PaintMethods, RuntimeManager};
pub(crate) struct DebugTerminal {
size: Size,
ansi_buffer: AnsiFormatter,
commands: VecDeque<Command>,
sys_events: VecDeque<SystemEvent>,
paint: bool,
ignore_paint_command: bool,
paint_title: String,
hash_to_test: Option<u64>,
cursor_point_to_check: Option<Point>,
mouse_pos: Point,
keymodifier_state: KeyModifier,
errors_disabled: bool,
clipboard_text: String,
}
impl DebugTerminal {
fn build_commands(script: &str) -> VecDeque<Command> {
let mut v: VecDeque<Command> = VecDeque::with_capacity(16);
for line in script.lines() {
let trim_line = line.trim();
if (trim_line.is_empty()) || (trim_line.starts_with(';')) || (trim_line.starts_with('#')) || (trim_line.starts_with("//")) {
continue;
}
match Command::new(line.trim()) {
Ok(cmd) => v.push_back(cmd),
Err(err) => {
println!("{}", err.as_string());
panic!()
}
}
}
v
}
pub(crate) fn new(builder: &crate::system::Builder) -> Result<Self, Error> {
let mut w = if builder.size.is_none() { 80 } else { builder.size.unwrap().width };
let mut h = if builder.size.is_none() { 40 } else { builder.size.unwrap().height };
w = w.clamp(10, 1000);
h = h.clamp(10, 1000);
let commands = DebugTerminal::build_commands(builder.debug_script.as_ref().unwrap().as_str());
Ok(DebugTerminal {
size: Size::new(w, h),
ansi_buffer: AnsiFormatter::new(
(w * h) as usize,
if builder.use_color_schema {
AnsiFlags::Use16ColorSchema
} else {
AnsiFlags::None
},
),
commands,
sys_events: VecDeque::with_capacity(8),
paint: false,
ignore_paint_command: false,
errors_disabled: false,
paint_title: String::new(),
hash_to_test: None,
cursor_point_to_check: None,
mouse_pos: Point::new(0, 0),
keymodifier_state: KeyModifier::None,
clipboard_text: String::new(),
})
}
fn compute_surface_hash(surface: &Surface) -> u64 {
let mut hash = 0xcbf29ce484222325u64;
let mut buf = [0u8; 8];
for ch in &surface.chars {
buf[0] = ((ch.code as u32) & 0xFF) as u8;
buf[1] = (((ch.code as u32) >> 8) & 0xFF) as u8;
buf[2] = (((ch.code as u32) >> 16) & 0xFF) as u8;
buf[3] = (((ch.code as u32) >> 24) & 0xFF) as u8;
buf[4] = ch.foreground.as_color_index();
buf[5] = ch.background.as_color_index();
buf[6] = ((ch.flags.get_value() >> 8) & 0xFF) as u8;
buf[7] = (ch.flags.get_value() & 0xFF) as u8;
for b in buf {
hash ^= b as u64;
hash = hash.wrapping_mul(0x00000100000001B3u64);
}
if let Some((r, g, b)) = ch.foreground.rgb() {
hash ^= r as u64;
hash = hash.wrapping_mul(0x00000100000001B3u64);
hash ^= g as u64;
hash = hash.wrapping_mul(0x00000100000001B3u64);
hash ^= b as u64;
hash = hash.wrapping_mul(0x00000100000001B3u64);
}
if let Some((r, g, b)) = ch.background.rgb() {
hash ^= r as u64;
hash = hash.wrapping_mul(0x00000100000001B3u64);
hash ^= g as u64;
hash = hash.wrapping_mul(0x00000100000001B3u64);
hash ^= b as u64;
hash = hash.wrapping_mul(0x00000100000001B3u64);
}
}
hash
}
}
impl Backend for DebugTerminal {
fn is_single_threaded(&self) -> bool {
true
}
fn update_screen(&mut self, surface: &Surface) {
let surface_hash = DebugTerminal::compute_surface_hash(surface);
if let Some(hash_to_test) = self.hash_to_test {
self.paint = false;
if hash_to_test != surface_hash {
if self.errors_disabled {
println!(
"\x1b[91;40m[Error] Invalid hash: (expecting: 0x{hash_to_test:X} but found 0x{surface_hash:X})\x1b[0m"
);
} else {
panic!(
"Invalid hash for surface (expecting: 0x{hash_to_test:X} but found 0x{surface_hash:X})"
);
}
}
}
let cursor = if !surface.cursor.is_visible() {
Point::new(-1, -1)
} else {
Point::new(surface.cursor.x as i32, surface.cursor.y as i32)
};
if let Some(point) = self.cursor_point_to_check {
if point != cursor {
let cursor_pos = format!("({},{})", cursor.x, cursor.y);
let cursor_repr = if cursor.x < 0 { "Hidden" } else { cursor_pos.as_str() };
let point_pos = format!("({},{})", point.x, point.y);
let point_repr = if point.x < 0 { "Hidden" } else { point_pos.as_str() };
if self.errors_disabled {
println!(
"\x1b[91;40m[Error] Invalid cursor position. Expectig the cursor to be {point_repr}, but found {cursor_repr}\x1b[0m"
);
} else {
panic!(
"Invalid cursor position. Expectig the cursor to be {point_repr}, but found {cursor_repr}"
);
}
}
}
self.hash_to_test = None;
self.cursor_point_to_check = None;
if !self.paint {
return;
}
self.paint = false;
println!();
self.ansi_buffer.clear();
for _ in 0..=6 + self.size.width {
self.ansi_buffer.write_char('=');
}
println!("+{}+", self.ansi_buffer.text());
self.ansi_buffer.clear();
self.ansi_buffer.write_string("| Name : ");
self.ansi_buffer.set_color(Color::Yellow, Color::Black);
self.ansi_buffer.write_string(&self.paint_title);
while self.ansi_buffer.text().len() < (self.size.width + 16) as usize {
self.ansi_buffer.write_char(' ');
}
self.ansi_buffer.reset_color();
println!("{}", &self.ansi_buffer.text());
self.ansi_buffer.clear();
self.ansi_buffer.write_string("| Hash : ");
self.ansi_buffer.set_color(Color::Yellow, Color::Black);
self.ansi_buffer.write_string(format!("0x{surface_hash:X}").as_str());
while self.ansi_buffer.text().len() < (self.size.width + 16) as usize {
self.ansi_buffer.write_char(' ');
}
self.ansi_buffer.reset_color();
println!("{}", &self.ansi_buffer.text());
self.ansi_buffer.clear();
self.ansi_buffer.write_string("| Cursor: ");
self.ansi_buffer.set_color(Color::Yellow, Color::Black);
if !surface.cursor.is_visible() {
self.ansi_buffer.write_string("Hidden");
} else {
self.ansi_buffer.write_string(format!("{},{}", cursor.x, cursor.y).as_str());
}
while self.ansi_buffer.text().len() < (self.size.width + 16) as usize {
self.ansi_buffer.write_char(' ');
}
self.ansi_buffer.reset_color();
println!("{}", &self.ansi_buffer.text());
self.ansi_buffer.clear();
self.ansi_buffer.write_char('|');
for _ in 0..=6 + self.size.width {
self.ansi_buffer.write_char('-');
}
self.ansi_buffer.write_char('|');
println!("{}", &self.ansi_buffer.text());
self.ansi_buffer.clear();
self.ansi_buffer.write_string("| | ");
for i in 0..self.size.width {
let digit = ((i % 100) / 10) as u8;
if (i as i32) == self.mouse_pos.x {
self.ansi_buffer.set_color(Color::White, Color::Red);
} else {
self.ansi_buffer.reset_color();
}
if digit == 0 {
self.ansi_buffer.write_char(' ');
} else {
self.ansi_buffer.write_char((48u8 + digit) as char);
}
}
println!("{}\x1b[0m |", self.ansi_buffer.text());
self.ansi_buffer.clear();
self.ansi_buffer.write_string("| | ");
for i in 0..self.size.width {
if (i as i32) == self.mouse_pos.x {
self.ansi_buffer.set_color(Color::White, Color::Red);
} else {
self.ansi_buffer.reset_color();
}
self.ansi_buffer.write_char((48u8 + ((i % 10) as u8)) as char);
}
println!("{}\x1b[0m |", self.ansi_buffer.text());
self.ansi_buffer.clear();
self.ansi_buffer.write_char('|');
for _ in 0..=6 + self.size.width {
self.ansi_buffer.write_char('-');
}
self.ansi_buffer.write_char('|');
println!("{}", &self.ansi_buffer.text());
self.ansi_buffer.clear();
let mut x = 0u32;
let mut y = 0u32;
for ch in &surface.chars {
let mut fore = ch.foreground;
let mut back = ch.background;
if (x as i32 == cursor.x) && (y as i32 == cursor.y) {
fore = ch.background;
back = ch.foreground;
}
self.ansi_buffer.set_color(fore, back);
self.ansi_buffer.set_char_flags(ch.flags);
if ch.code <= ' ' {
self.ansi_buffer.write_char(' ');
} else {
self.ansi_buffer.write_char(ch.code);
}
self.ansi_buffer.reset_color();
self.ansi_buffer.set_char_flags(CharFlags::None);
x += 1;
if x == self.size.width {
if (y as i32) == self.mouse_pos.y {
println!("|\x1b[97m\x1b[41m{:>3} \x1b[0m| {} |", y, &self.ansi_buffer.text());
} else {
println!("|{:>3} | {} |", y, &self.ansi_buffer.text());
}
self.ansi_buffer.clear();
x = 0;
y += 1;
}
}
self.ansi_buffer.write_char('|');
for _ in 0..=6 + self.size.width {
self.ansi_buffer.write_char('-');
}
self.ansi_buffer.write_char('|');
println!("{}", &self.ansi_buffer.text());
}
fn size(&self) -> Size {
self.size
}
fn query_system_event(&mut self) -> Option<SystemEvent> {
if let Some(event) = self.sys_events.pop_front() {
match event {
SystemEvent::Resize(new_size) => {
self.size.width = new_size.width;
self.size.height = new_size.height;
}
SystemEvent::MouseButtonDown(evnt) => {
self.mouse_pos.x = evnt.x;
self.mouse_pos.y = evnt.y;
}
SystemEvent::MouseButtonUp(evnt) => {
self.mouse_pos.x = evnt.x;
self.mouse_pos.y = evnt.y;
}
SystemEvent::MouseDoubleClick(evnt) => {
self.mouse_pos.x = evnt.x;
self.mouse_pos.y = evnt.y;
}
SystemEvent::MouseMove(evnt) => {
self.mouse_pos.x = evnt.x;
self.mouse_pos.y = evnt.y;
}
SystemEvent::MouseWheel(evnt) => {
self.mouse_pos.x = evnt.x;
self.mouse_pos.y = evnt.y;
}
SystemEvent::KeyModifierChanged(evnt) => {
self.keymodifier_state = evnt.new_state;
}
_ => {}
}
return Some(event);
}
if let Some(cmd) = self.commands.pop_front() {
cmd.generate_event(self.mouse_pos, self.keymodifier_state, &mut self.sys_events);
if !self.ignore_paint_command {
if let Some(title) = cmd.get_paint_command_title() {
self.paint_title = title;
RuntimeManager::get().request_repaint();
self.paint = true;
return None;
}
}
match cmd {
Command::MouseHold(_)
| Command::MouseRelease(_)
| Command::MouseClick(_)
| Command::MouseDoubleClick(_)
| Command::MouseMove(_)
| Command::MouseDrag(_)
| Command::MouseWheel(_)
| Command::Paint(_)
| Command::Resize(_)
| Command::KeyPresed(_)
| Command::KeyModifier(_)
| Command::KeyTypeText(_) => {
return None;
}
Command::PaintEnable(obj) => {
self.ignore_paint_command = !obj.is_paint_enabled();
return None;
}
Command::ErrorDisable(obj) => {
self.errors_disabled = obj.is_error_disabled();
return None;
}
Command::CheckHash(obj) => {
self.paint = false; self.hash_to_test = Some(obj.get_hash()); RuntimeManager::get().request_repaint();
return None;
}
Command::CheckCursor(obj) => {
self.paint = false; self.cursor_point_to_check = Some(obj.get_point()); RuntimeManager::get().request_repaint();
return None;
}
Command::ClipboardSetText(obj) => {
self.set_clipboard_text(obj.get_text());
return None;
}
Command::ClipboardClear(_) => {
self.set_clipboard_text("");
return None;
}
Command::CheckClipboardText(obj) => {
if obj.get_text() != self.clipboard_text {
if self.errors_disabled {
println!(
"\x1b[91;40m[Error] Invalid clipboard text: (expecting: '{}' but found '{}')\x1b[0m",
obj.get_text(),
self.clipboard_text
);
} else {
panic!(
"Invalid clipboard text: (expecting: '{}' but found '{}')",
obj.get_text(),
self.clipboard_text
);
}
}
return None;
}
}
}
Some(SystemEvent::AppClose)
}
fn clipboard_text(&self) -> Option<String> {
if self.clipboard_text.is_empty() {
None
} else {
Some(self.clipboard_text.clone())
}
}
fn set_clipboard_text(&mut self, text: &str) {
self.clipboard_text.clear();
self.clipboard_text.push_str(text);
}
fn has_clipboard_text(&self) -> bool {
!self.clipboard_text.is_empty()
}
fn on_resize(&mut self, new_size: Size) {
self.size = new_size;
}
}