main.rs - icy_draw - [fork] icy_draw is the successor to mystic draw.
 (HTM) git clone https://git.drkhsh.at/icy_draw.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
       main.rs (8035B)
       ---
            1 use clap::{Parser, Subcommand};
            2 use icy_engine::{update_crc32, Buffer, SaveOptions, TextPane};
            3 use icy_engine_gui::animations::Animator;
            4 use std::{fs, path::PathBuf, thread, time::Duration};
            5 
            6 use crate::com::Com;
            7 
            8 mod com;
            9 
           10 #[derive(Clone, Debug, PartialEq, Eq)]
           11 pub enum Terminal {
           12     IcyTerm,
           13     SyncTerm,
           14     Unknown,
           15     Name(String),
           16 }
           17 
           18 impl Terminal {
           19     pub fn use_dcs(&self) -> bool {
           20         matches!(self, Terminal::IcyTerm)
           21     }
           22 
           23     fn can_repeat_rle(&self) -> bool {
           24         matches!(self, Terminal::IcyTerm | Terminal::SyncTerm)
           25     }
           26 }
           27 
           28 #[derive(Parser)]
           29 pub struct Cli {
           30     #[arg(help = "If true modern terminal output (UTF8) is used.", long, default_value_t = false)]
           31     utf8: bool,
           32 
           33     #[arg(help = "File to play/show.", required = true)]
           34     path: Option<PathBuf>,
           35 
           36     #[arg(help = "Socket port address for i/o", long)]
           37     port: Option<String>,
           38 
           39     #[command(subcommand)]
           40     command: Option<Commands>,
           41 }
           42 
           43 #[derive(Subcommand)]
           44 enum Commands {
           45     #[command(about = "Plays the animation (default)")]
           46     Play,
           47 
           48     #[command(about = "Show a specific frame of the animation")]
           49     ShowFrame { frame: usize },
           50 }
           51 
           52 pub fn get_line_checksums(buf: &Buffer) -> Vec<u32> {
           53     let mut result = Vec::new();
           54     for y in 0..buf.get_height() {
           55         let mut crc = 0;
           56         for x in 0..buf.get_width() {
           57             let ch = buf.get_char((x, y));
           58             crc = update_crc32(crc, ch.ch as u8);
           59             let fg = buf.palette.get_color(ch.attribute.get_foreground()).get_rgb();
           60             crc = update_crc32(crc, fg.0);
           61             crc = update_crc32(crc, fg.1);
           62             crc = update_crc32(crc, fg.2);
           63             let bg = buf.palette.get_color(ch.attribute.get_background()).get_rgb();
           64             crc = update_crc32(crc, bg.0);
           65             crc = update_crc32(crc, bg.1);
           66             crc = update_crc32(crc, bg.2);
           67             crc = update_crc32(crc, ch.attribute.attr as u8);
           68             crc = update_crc32(crc, (ch.attribute.attr >> 8) as u8);
           69         }
           70         result.push(crc);
           71     }
           72     result
           73 }
           74 
           75 fn main() {
           76     let args = Cli::parse();
           77 
           78     let mut io: Box<dyn Com> = if let Some(port) = args.port {
           79         Box::new(com::SocketCom::connect("127.0.0.1:".to_string() + port.as_str()).unwrap())
           80     } else {
           81         Box::new(com::StdioCom::start().unwrap())
           82     };
           83 
           84     if let Some(path) = args.path {
           85         let parent = Some(path.parent().unwrap().to_path_buf());
           86 
           87         let Some(ext) = path.extension() else {
           88             println!("Error: File extension not found.");
           89             return;
           90         };
           91         let ext = ext.to_string_lossy().to_ascii_lowercase();
           92         let mut term = Terminal::Unknown;
           93 
           94         match ext.as_str() {
           95             "icyanim" => match fs::read_to_string(path) {
           96                 Ok(txt) => {
           97                     let animator = Animator::run(&parent, txt);
           98                     animator.lock().unwrap().set_is_playing(true);
           99 
          100                     let mut opt: SaveOptions = SaveOptions::default();
          101                     if args.utf8 {
          102                         opt.modern_terminal_output = true;
          103                     }
          104                     match args.command.unwrap_or(Commands::Play) {
          105                         Commands::Play => {
          106                             io.write(b"\x1B[0c").unwrap();
          107                             match io.read(true) {
          108                                 Ok(Some(data)) => {
          109                                     let txt: String = String::from_utf8_lossy(&data).to_string();
          110                                     term = if txt.contains("73;99;121;84;101;114;109") {
          111                                         Terminal::IcyTerm
          112                                     } else if txt.contains("67;84;101;114") {
          113                                         Terminal::SyncTerm
          114                                     } else {
          115                                         Terminal::Name(txt)
          116                                     }
          117                                 } // 67;84;101;114;109;1;316
          118                                 Ok(_) | Err(_) => {
          119                                     // ignore (timeout)
          120                                 }
          121                             }
          122                             // flush.
          123                             while let Ok(Some(_)) = io.read(false) {}
          124                             let mut checksums = Vec::new();
          125 
          126                             // turn caret off
          127                             let _ = io.write(b"\x1b[?25l");
          128 
          129                             while animator.lock().unwrap().is_playing() {
          130                                 if let Ok(Some(v)) = io.read(false) {
          131                                     if v.contains(&b'\x1b') || v.contains(&b'\n') || v.contains(&b' ') {
          132                                         break;
          133                                     }
          134                                 }
          135                                 if let Some((buffer, _, delay)) = animator.lock().unwrap().get_cur_frame_buffer() {
          136                                     let new_checksums = get_line_checksums(buffer);
          137                                     let mut skip_lines = Vec::new();
          138                                     if checksums.len() == new_checksums.len() {
          139                                         for i in 0..checksums.len() {
          140                                             if checksums[i] == new_checksums[i] {
          141                                                 skip_lines.push(i);
          142                                             }
          143                                         }
          144                                     }
          145                                     show_buffer(&mut io, buffer, false, args.utf8, &term, skip_lines).unwrap();
          146                                     checksums = new_checksums;
          147                                     std::thread::sleep(Duration::from_millis(*delay as u64));
          148                                 } else {
          149                                     thread::sleep(Duration::from_millis(10));
          150                                 }
          151                                 while !animator.lock().unwrap().next_frame() {
          152                                     thread::sleep(Duration::from_millis(10));
          153                                 }
          154                             }
          155                             let _ = io.write(b"\x1b[?25h\x1b[0;0 D");
          156                         }
          157                         Commands::ShowFrame { frame } => {
          158                             show_buffer(&mut io, &animator.lock().unwrap().frames[frame].0, true, args.utf8, &term, Vec::new()).unwrap();
          159                         }
          160                     }
          161                 }
          162                 Err(e) => {
          163                     println!("Error opening file: {e}");
          164                 }
          165             },
          166             _ => {
          167                 let buffer = Buffer::load_buffer(&path, true);
          168                 if let Ok(buffer) = &buffer {
          169                     show_buffer(&mut io, buffer, true, args.utf8, &Terminal::Unknown, Vec::new()).unwrap();
          170                 }
          171             }
          172         }
          173     }
          174 }
          175 
          176 fn show_buffer(io: &mut Box<dyn Com>, buffer: &Buffer, single_frame: bool, use_utf8: bool, terminal: &Terminal, skip_lines: Vec<usize>) -> anyhow::Result<()> {
          177     let mut opt: SaveOptions = SaveOptions::default();
          178     if use_utf8 {
          179         opt.modern_terminal_output = true;
          180     }
          181     opt.control_char_handling = icy_engine::ControlCharHandling::FilterOut;
          182     opt.longer_terminal_output = true;
          183     opt.compress = true;
          184     opt.use_cursor_forward = false;
          185     opt.preserve_line_length = true;
          186     opt.use_repeat_sequences = terminal.can_repeat_rle();
          187 
          188     opt.skip_lines = Some(skip_lines);
          189 
          190     if matches!(terminal, Terminal::IcyTerm) {
          191         opt.control_char_handling = icy_engine::ControlCharHandling::IcyTerm;
          192     }
          193     let bytes = buffer.to_bytes("ans", &opt)?;
          194 
          195     if !single_frame && terminal.use_dcs() {
          196         io.write(b"\x1BP0;1;0!z")?;
          197     }
          198     io.write(&bytes)?;
          199     /*for i in 0..buffer.get_height() {
          200         io.write(format!("\x1b[{};1H{}:", i + 1, i).as_bytes())?;
          201     }
          202     */
          203     //io.write(format!("\x1b[23;1HTerminal:{:?}", terminal).as_bytes())?;
          204     if !single_frame && terminal.use_dcs() {
          205         io.write(b"\x1b\\\x1b[0*z")?;
          206     }
          207     Ok(())
          208 }