use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result;
use std::fs;
use crate::backend::Backend;
use crate::graphics::*;
use crate::input::*;
use crate::system::MouseButtonDownEvent;
use crate::system::MouseButtonUpEvent;
use crate::system::MouseMoveEvent;
use crate::system::MouseWheelEvent;
use crate::system::SystemEvent;
use appcui_proc_macro::*;
use super::RuntimeManager;
enum MouseUpPossibleCombineAction {
Up,
Click,
Drag,
}
struct KeyPressed {
key: Key,
times: u32,
}
struct MouseWheel {
x: i32,
y: i32,
dir: MouseWheelDirection,
times: u32,
}
struct PaintCommand {
state_name: String,
}
struct MouseClick {
x: i32,
y: i32,
button: MouseButton,
}
struct MouseDrag {
x1: i32,
y1: i32,
x2: i32,
y2: i32,
}
enum Command {
KeyPressed(KeyPressed),
Resize(Size),
MouseMove(MouseMoveEvent),
MouseHold(MouseButtonDownEvent),
MouseRelease(MouseButtonUpEvent),
MouseWheel(MouseWheel),
MouseClick(MouseClick),
MouseDrag(MouseDrag),
Paint(PaintCommand),
CheckHash(u64),
}
impl Display for Command {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let var_name = match self {
Command::KeyPressed(cmd) => {
if cmd.times > 1 {
write!(f, "Key.Pressed({},{})\n", cmd.key, cmd.times)
} else {
write!(f, "Key.Pressed({})\n", cmd.key)
}
}
Command::Resize(sz) => write!(f, "Resize({},{})\n", sz.width, sz.height),
Command::MouseMove(cmd) => write!(f, "Mouse.Move({},{})\n", cmd.x, cmd.y),
Command::MouseHold(cmd) => write!(f, "Mouse.Hold({},{},{})\n", cmd.x, cmd.y, cmd.button.name()),
Command::MouseRelease(cmd) => write!(f, "Mouse.Release({},{},left)\n", cmd.x, cmd.y),
Command::MouseClick(cmd) => write!(f, "Mouse.Click({},{},{})\n", cmd.x, cmd.y, cmd.button.name()),
Command::MouseDrag(cmd) => write!(f, "Mouse.Drag({},{},{},{})\n", cmd.x1, cmd.y1, cmd.x2, cmd.y2),
Command::MouseWheel(cmd) => {
if cmd.times > 1 {
write!(f, "Mouse.Wheel({},{},{},{})\n", cmd.x, cmd.y, cmd.dir.name(), cmd.times)
} else {
write!(f, "Mouse.Wheel({},{},{})\n", cmd.x, cmd.y, cmd.dir.name())
}
}
Command::Paint(cmd) => write!(f, "Paint('{}')\n", cmd.state_name),
Command::CheckHash(hash) => write!(f, "CheckHash(0x{:x})\n", hash),
};
var_name
}
}
pub(super) struct EventRecorder {
commands: Vec<Command>,
state_id: u32,
auto_mode: bool,
last_hash: u64,
}
impl EventRecorder {
pub(super) fn new() -> Self {
Self {
commands: Vec::with_capacity(512),
state_id: 1,
auto_mode: false,
last_hash: 0,
}
}
pub(super) fn save(&self) {
let mut content = String::with_capacity(self.commands.len() * 32);
let mut step = String::with_capacity(256);
content.push_str("Paint.Enable(false)\n");
for cmd in &self.commands {
step += cmd.to_string().as_str();
match cmd {
Command::CheckHash(_) => {
content += step.as_str();
step.clear();
}
_ => {}
}
}
let _ = fs::write("events.txt", content);
}
pub(super) fn add(&mut self, sys_event: &SystemEvent, backend: &mut Box<dyn Backend>, surface: &Surface) {
match sys_event {
SystemEvent::AppClose => {}
SystemEvent::KeyPressed(event) => {
if self.add_keypressed(event.key) {
self.save_state(backend, surface);
RuntimeManager::get().request_update();
}
}
SystemEvent::KeyModifierChanged(_) => {}
SystemEvent::Resize(new_size) => self.add_resize(*new_size),
SystemEvent::MouseButtonDown(evnt) => self.add_mouse_button_down(evnt),
SystemEvent::MouseButtonUp(evnt) => self.add_mouse_button_up(evnt),
SystemEvent::MouseDoubleClick(_) => {}
SystemEvent::MouseMove(evnt) => self.add_mouse_move(evnt),
SystemEvent::MouseWheel(evnt) => self.add_mouse_wheel(evnt),
_ => {}
}
}
pub(super) fn auto_update(&mut self, surface: &Surface) {
if !self.auto_mode {
return;
}
let hash = EventRecorder::compute_surface_hash(surface);
if hash != self.last_hash {
self.last_hash = hash;
self.state_id += 1;
let state_id = self.state_id;
self.commands.push(Command::Paint(PaintCommand {
state_name: format!("State_{}", state_id),
}));
self.commands.push(Command::CheckHash(hash));
}
}
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 = 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);
}
}
return hash;
}
fn add_keypressed(&mut self, key: Key) -> bool {
if key.value() == key!("Ctrl+Alt+Space") {
return true;
}
if let Some(last) = self.commands.last_mut() {
match last {
Command::KeyPressed(c) => {
if c.key == key {
c.times += 1;
return false;
}
}
_ => {}
}
}
self.commands.push(Command::KeyPressed(KeyPressed { key, times: 1 }));
return false;
}
fn add_resize(&mut self, new_size: Size) {
if let Some(last) = self.commands.last_mut() {
match last {
Command::Resize(sz) => {
sz.width = new_size.width;
sz.height = new_size.height;
return;
}
_ => {}
}
}
self.commands.push(Command::Resize(new_size));
}
fn add_mouse_move(&mut self, evnt: &MouseMoveEvent) {
if let Some(last) = self.commands.last_mut() {
match last {
Command::MouseMove(cmd) => {
cmd.x = evnt.x;
cmd.y = evnt.y;
return;
}
_ => {}
}
}
self.commands.push(Command::MouseMove(*evnt));
}
fn add_mouse_button_down(&mut self, evnt: &MouseButtonDownEvent) {
self.commands.push(Command::MouseHold(*evnt));
}
fn add_mouse_button_up(&mut self, evnt: &MouseButtonUpEvent) {
let mut action = MouseUpPossibleCombineAction::Up;
if let Some(last) = self.commands.last() {
match last {
Command::MouseHold(cmd) => {
if (cmd.x == evnt.x) && (cmd.y == evnt.y) {
action = MouseUpPossibleCombineAction::Click;
}
}
Command::MouseMove(_) => {
if self.commands.len() >= 2 {
match self.commands[self.commands.len() - 2] {
Command::MouseHold(_) => {
action = MouseUpPossibleCombineAction::Drag;
}
_ => {}
}
}
}
_ => {}
}
}
match action {
MouseUpPossibleCombineAction::Up => {
self.commands.push(Command::MouseRelease(*evnt));
}
MouseUpPossibleCombineAction::Click => {
let button = match self.commands.pop().unwrap() {
Command::MouseHold(cmd) => cmd.button,
_ => MouseButton::None,
};
self.commands.push(Command::MouseClick(MouseClick {
x: evnt.x,
y: evnt.y,
button,
}));
}
MouseUpPossibleCombineAction::Drag => {
let _ = self.commands.pop(); let (start_x, start_y) = match self.commands.pop().unwrap() {
Command::MouseHold(cmd) => (cmd.x, cmd.y),
_ => (0, 0),
};
self.commands.push(Command::MouseDrag(MouseDrag {
x1: start_x,
y1: start_y,
x2: evnt.x,
y2: evnt.y,
}));
}
}
}
fn add_mouse_wheel(&mut self, evnt: &MouseWheelEvent) {
if let Some(last) = self.commands.last_mut() {
match last {
Command::MouseWheel(cmd) => {
if (cmd.x == evnt.x) && (cmd.y == evnt.y) && (cmd.dir == evnt.direction) {
cmd.times += 1;
return;
}
}
_ => {}
}
}
self.commands.push(Command::MouseWheel(MouseWheel {
x: evnt.x,
y: evnt.y,
dir: evnt.direction,
times: 1,
}));
}
fn print_hot_key(key: &str, text: &str, x: i32, surface: &mut Surface) {
let y = 3;
surface.write_char(x, y, Character::new('[', Color::Gray, Color::DarkBlue, CharFlags::None));
surface.write_string(x + 1, y, key, CharAttribute::with_color(Color::Aqua, Color::DarkBlue), false);
let key_len = key.len() as i32;
surface.write_char(x + 1 + key_len, y, Character::new(' ', Color::Gray, Color::DarkBlue, CharFlags::None));
surface.write_string(x + 2 + key_len, y, text, CharAttribute::with_color(Color::Silver, Color::DarkBlue), false);
let text_len = text.len() as i32;
surface.write_char(
x + 2 + text_len + key_len,
y,
Character::new(']', Color::Gray, Color::DarkBlue, CharFlags::None),
);
}
fn save_state(&mut self, backend: &mut Box<dyn Backend>, surface: &Surface) {
let sz = surface.size();
let mut screen = Surface::new(sz.width, sz.height);
let mut state_name = format!("State_{}", self.state_id);
let mut comands = format!("Commands: {}", self.commands.len());
let mut auto = String::from(if self.auto_mode { "Auto:ON" } else { "Auto:OFF" });
loop {
screen.clear(Character::new(' ', Color::White, Color::Black, CharFlags::None));
screen.draw_surface(0, 0, surface);
screen.clear(Character::with_color(Color::Gray, Color::Black));
screen.fill_rect(
Rect::new(0, 0, (sz.width as i32) - 1, 2),
Character::new(' ', Color::White, Color::DarkBlue, CharFlags::None),
);
screen.draw_rect(
Rect::new(0, 0, (sz.width as i32) - 1, 3),
LineType::Single,
CharAttribute::with_color(Color::White, Color::DarkBlue),
);
screen.write_string(1, 1, "State name:", CharAttribute::with_fore_color(Color::Silver), false);
screen.write_string(1, 2, &comands, CharAttribute::with_fore_color(Color::Gray), false);
screen.fill_horizontal_line(
12,
1,
(sz.width as i32) - 2,
Character::new(' ', Color::White, Color::Black, CharFlags::None),
);
screen.write_string(13, 1, &state_name, CharAttribute::with_fore_color(Color::White), false);
screen.set_cursor(13 + state_name.chars().count() as i32, 1);
EventRecorder::print_hot_key("Esc", "Exit", 2, &mut screen);
EventRecorder::print_hot_key("Enter", "Add", 13, &mut screen);
EventRecorder::print_hot_key("F8", "Clear All", 25, &mut screen);
EventRecorder::print_hot_key("F9", &auto, 40, &mut screen);
backend.update_screen(&screen);
if let Some(sys_event) = backend.query_system_event() {
match sys_event {
SystemEvent::KeyPressed(evnt) => match evnt.key.value() {
key!("Escape") => {
return;
}
key!("Enter") => {
self.state_id += 1;
self.commands.push(Command::Paint(PaintCommand { state_name }));
self.commands.push(Command::CheckHash(EventRecorder::compute_surface_hash(surface)));
return;
}
key!("F8") => {
self.commands.clear();
comands = format!("Commands: {}", self.commands.len());
}
key!("F9") => {
self.auto_mode = !self.auto_mode;
auto.clear();
auto.push_str(if self.auto_mode { "Auto:ON" } else { "Auto:OFF" });
}
key!("Backspace") => {
state_name.pop();
}
_ => {
if evnt.character >= ' ' {
state_name.push(evnt.character);
}
}
},
_ => {}
}
}
}
}
}