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 }