mod.rs - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
 (HTM) git clone https://git.drkhsh.at/icy_draw.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       mod.rs (17863B)
       ---
            1 use std::{
            2     path::{Path, PathBuf},
            3     sync::{mpsc::Receiver, Arc},
            4     time::Instant,
            5 };
            6 
            7 use crate::{model::Tool, AnsiEditor, ClipboardHandler, Document, DocumentOptions, Message, TerminalResult, UndoHandler};
            8 use eframe::{
            9     egui::{self, Id, ImageButton, RichText, Slider, TextEdit, TopBottomPanel},
           10     epaint::Vec2,
           11 };
           12 use egui::{Image, ProgressBar};
           13 use egui_code_editor::{CodeEditor, Syntax};
           14 use i18n_embed_fl::fl;
           15 use icy_engine::{ascii, AttributedChar, Buffer, EngineResult, Size, TextAttribute, UnicodeConverter};
           16 use icy_engine_egui::{animations::Animator, show_terminal_area, BufferView, MonitorSettings};
           17 
           18 use self::encoding::{start_encoding_thread, ENCODERS};
           19 mod asciicast_encoder;
           20 mod encoding;
           21 mod gif_encoder;
           22 mod highlighting;
           23 //mod mp4_encoder;
           24 
           25 pub struct AnimationEditor {
           26     gl: Arc<glow::Context>,
           27     id: usize,
           28 
           29     undostack: usize,
           30 
           31     txt: String,
           32     buffer_view: Arc<eframe::epaint::mutex::Mutex<BufferView>>,
           33     animator: Arc<std::sync::Mutex<Animator>>,
           34     next_animator: Option<Arc<std::sync::Mutex<Animator>>>,
           35     set_frame: usize,
           36 
           37     parent_path: Option<PathBuf>,
           38     export_path: PathBuf,
           39     export_type: usize,
           40 
           41     first_frame: bool,
           42 
           43     shedule_update: bool,
           44     last_update: Instant,
           45     cursor_index: usize,
           46     scale: f32,
           47 
           48     rx: Option<Receiver<usize>>,
           49     thread: Option<std::thread::JoinHandle<TerminalResult<()>>>,
           50     cur_encoding_frame: usize,
           51     encoding_frames: usize,
           52     encoding_error: String,
           53 }
           54 
           55 impl AnimationEditor {
           56     pub fn new(gl: Arc<glow::Context>, id: usize, path: &Path, txt: String) -> Self {
           57         let mut buffer = Buffer::new(Size::new(80, 25));
           58         buffer.is_terminal_buffer = false;
           59         let mut buffer_view = BufferView::from_buffer(&gl, buffer);
           60         buffer_view.interactive = false;
           61         let buffer_view = Arc::new(eframe::epaint::mutex::Mutex::new(buffer_view));
           62         let parent_path = path.parent().map(|p| p.to_path_buf());
           63         let animator = Animator::run(&parent_path, txt.clone());
           64         let export_path = path.with_extension("gif");
           65         Self {
           66             gl,
           67             id,
           68             buffer_view,
           69             animator,
           70             txt,
           71             undostack: 0,
           72             export_path,
           73             export_type: 0,
           74             parent_path,
           75             set_frame: 0,
           76             scale: 1.0,
           77             next_animator: None,
           78             shedule_update: false,
           79             last_update: Instant::now(),
           80             first_frame: true,
           81             rx: None,
           82             thread: None,
           83             cur_encoding_frame: 0,
           84             encoding_frames: 0,
           85             cursor_index: 0,
           86             encoding_error: String::new(),
           87         }
           88     }
           89 
           90     fn export(&mut self) -> TerminalResult<()> {
           91         if let Some((rx, handle)) = start_encoding_thread(self.export_type, self.gl.clone(), self.export_path.clone(), self.animator.clone())? {
           92             self.rx = Some(rx);
           93             self.thread = Some(handle);
           94             self.encoding_frames = self.animator.lock().unwrap().frames.len();
           95         }
           96         Ok(())
           97     }
           98 }
           99 
          100 impl ClipboardHandler for AnimationEditor {
          101     fn can_copy(&self) -> bool {
          102         false
          103     }
          104 
          105     fn copy(&mut self) -> EngineResult<()> {
          106         Ok(())
          107     }
          108 
          109     fn can_paste(&self) -> bool {
          110         false
          111     }
          112 
          113     fn paste(&mut self) -> EngineResult<()> {
          114         Ok(())
          115     }
          116 }
          117 
          118 impl UndoHandler for AnimationEditor {
          119     fn undo_description(&self) -> Option<String> {
          120         None
          121     }
          122 
          123     fn can_undo(&self) -> bool {
          124         false
          125     }
          126 
          127     fn undo(&mut self) -> EngineResult<Option<Message>> {
          128         Ok(None)
          129     }
          130 
          131     fn redo_description(&self) -> Option<String> {
          132         None
          133     }
          134 
          135     fn can_redo(&self) -> bool {
          136         false
          137     }
          138 
          139     fn redo(&mut self) -> EngineResult<Option<Message>> {
          140         Ok(None)
          141     }
          142 }
          143 
          144 impl Document for AnimationEditor {
          145     fn default_extension(&self) -> &'static str {
          146         "icyanim"
          147     }
          148 
          149     fn undo_stack_len(&self) -> usize {
          150         self.undostack
          151     }
          152 
          153     fn can_paste_char(&self) -> bool {
          154         true
          155     }
          156 
          157     fn paste_char(&mut self, _ui: &mut eframe::egui::Ui, ch: char) {
          158         let ch = ascii::CP437Converter::default().convert_to_unicode(AttributedChar::new(ch, TextAttribute::default()));
          159         self.txt.insert(self.cursor_index, ch);
          160         if let Some((i, _)) = self.txt.char_indices().nth(self.cursor_index + 1) {
          161             self.cursor_index = i;
          162         }
          163     }
          164 
          165     fn show_ui(&mut self, ui: &mut eframe::egui::Ui, _cur_tool: &mut Box<dyn Tool>, _selected_tool: usize, _options: &DocumentOptions) -> Option<Message> {
          166         let mut message = None;
          167 
          168         if self.first_frame && self.animator.lock().unwrap().success() {
          169             let animator = &mut self.animator.lock().unwrap();
          170             let frame_count = animator.frames.len();
          171             if frame_count > 0 {
          172                 animator.set_cur_frame(self.set_frame);
          173                 animator.display_frame(self.buffer_view.clone());
          174             }
          175             self.first_frame = false;
          176         }
          177         if let Some(next) = &self.next_animator {
          178             if next.lock().unwrap().success() || !next.lock().unwrap().error.is_empty() {
          179                 self.animator = next.clone();
          180                 self.next_animator = None;
          181                 let animator = &mut self.animator.lock().unwrap();
          182                 animator.set_cur_frame(self.set_frame);
          183                 animator.display_frame(self.buffer_view.clone());
          184             }
          185         }
          186 
          187         egui::SidePanel::right("movie_panel")
          188             .default_width(ui.available_width() / 2.0)
          189             .min_width(660.0)
          190             .show_inside(ui, |ui| {
          191                 ui.horizontal(|ui| {
          192                     if !self.animator.lock().unwrap().error.is_empty() {
          193                         ui.set_enabled(false);
          194                     }
          195 
          196                     if self.animator.lock().unwrap().success() {
          197                         let animator = &mut self.animator.lock().unwrap();
          198                         let frame_count = animator.frames.len();
          199                         if animator.is_playing() {
          200                             if ui.add(ImageButton::new(crate::PAUSE_SVG.clone())).clicked() {
          201                                 animator.set_is_playing(false);
          202                             }
          203                         } else {
          204                             let image: &Image<'static> = if animator.get_cur_frame() + 1 < frame_count {
          205                                 &crate::PLAY_SVG
          206                             } else {
          207                                 &crate::REPLAY_SVG
          208                             };
          209                             if ui.add(ImageButton::new(image.clone())).clicked() {
          210                                 if animator.get_cur_frame() + 1 >= frame_count {
          211                                     animator.set_cur_frame(0);
          212                                 }
          213                                 animator.start_playback(self.buffer_view.clone());
          214                             }
          215                         }
          216                         if ui
          217                             .add_enabled(animator.get_cur_frame() + 1 < frame_count, ImageButton::new(crate::SKIP_NEXT_SVG.clone()))
          218                             .clicked()
          219                         {
          220                             animator.set_cur_frame(frame_count - 1);
          221                             animator.display_frame(self.buffer_view.clone());
          222                         }
          223                         let is_loop = animator.get_is_loop();
          224                         if ui.add(ImageButton::new(crate::REPEAT_SVG.clone()).selected(is_loop)).clicked() {
          225                             animator.set_is_loop(!is_loop);
          226                         }
          227 
          228                         let mut cf = animator.get_cur_frame() + 1;
          229 
          230                         if frame_count > 0
          231                             && ui
          232                                 .add(Slider::new(&mut cf, 1..=frame_count).text(fl!(crate::LANGUAGE_LOADER, "animation_of_frame_count", total = frame_count)))
          233                                 .changed()
          234                         {
          235                             animator.set_cur_frame(cf - 1);
          236                             animator.display_frame(self.buffer_view.clone());
          237                         }
          238 
          239                         if ui
          240                             .add_enabled(animator.get_cur_frame() > 0, ImageButton::new(crate::NAVIGATE_PREV.clone()))
          241                             .clicked()
          242                         {
          243                             let cf = animator.get_cur_frame() - 1;
          244                             animator.set_cur_frame(cf);
          245                             animator.display_frame(self.buffer_view.clone());
          246                         }
          247 
          248                         if ui
          249                             .add_enabled(
          250                                 animator.get_cur_frame() + 1 < animator.frames.len(),
          251                                 ImageButton::new(crate::NAVIGATE_NEXT.clone()),
          252                             )
          253                             .clicked()
          254                         {
          255                             let cf = animator.get_cur_frame() + 1;
          256                             animator.set_cur_frame(cf);
          257                             animator.display_frame(self.buffer_view.clone());
          258                         }
          259 
          260                         ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
          261                             if ui.button(if self.scale < 2.0 { "2x" } else { "1x" }).clicked() {
          262                                 if self.scale < 2.0 {
          263                                     self.scale = 2.0;
          264                                 } else {
          265                                     self.scale = 1.0;
          266                                 }
          267                             }
          268                         });
          269                     }
          270                 });
          271 
          272                 if self.animator.lock().unwrap().success() {
          273                     let cur_frame = self.animator.lock().unwrap().get_cur_frame();
          274 
          275                     let monitor_settings = if let Some((_, settings, _)) = self.animator.lock().unwrap().frames.get(cur_frame) {
          276                         settings.clone()
          277                     } else {
          278                         MonitorSettings::default()
          279                     };
          280                     let mut scale = Vec2::splat(self.scale);
          281                     if self.buffer_view.lock().get_buffer().use_aspect_ratio() {
          282                         scale.y *= 1.35;
          283                     }
          284                     let opt = icy_engine_egui::TerminalOptions {
          285                         stick_to_bottom: false,
          286                         scale: Some(scale),
          287                         monitor_settings,
          288                         id: Some(Id::new(self.id + 20000)),
          289                         ..Default::default()
          290                     };
          291                     ui.allocate_ui(Vec2::new(ui.available_width(), ui.available_height() - 100.0), |ui| {
          292                         self.buffer_view.lock().get_caret_mut().set_is_visible(false);
          293                         let (_, _) = show_terminal_area(ui, self.buffer_view.clone(), opt);
          294                     });
          295                     ui.add_space(8.0);
          296                 }
          297 
          298                 if let Some(rx) = &self.rx {
          299                     if let Ok(x) = rx.recv() {
          300                         self.cur_encoding_frame = x;
          301                     }
          302 
          303                     ui.label(fl!(
          304                         crate::LANGUAGE_LOADER,
          305                         "animation_encoding_frame",
          306                         cur = self.cur_encoding_frame,
          307                         total = self.encoding_frames
          308                     ));
          309                     ui.add(ProgressBar::new(self.cur_encoding_frame as f32 / self.encoding_frames as f32));
          310                     if self.cur_encoding_frame >= self.encoding_frames {
          311                         if let Some(thread) = self.thread.take() {
          312                             if let Ok(Err(err)) = thread.join() {
          313                                 log::error!("Error during encoding: {err}");
          314                                 self.encoding_error = format!("{err}");
          315                             }
          316                         }
          317                         self.rx = None;
          318                     } else if let Some(thread) = &self.thread {
          319                         if thread.is_finished() {
          320                             if let Err(err) = self.thread.take().unwrap().join() {
          321                                 let msg = if let Some(msg) = err.downcast_ref::<&'static str>() {
          322                                     msg.to_string()
          323                                 } else if let Some(msg) = err.downcast_ref::<String>() {
          324                                     msg.clone()
          325                                 } else {
          326                                     format!("?{:?}", err)
          327                                 };
          328                                 log::error!("Error during encoding: {:?}", msg);
          329                                 self.encoding_error = format!("Thread aborted: {:?}", msg);
          330                             }
          331                             self.rx = None;
          332                         }
          333                     }
          334                 } else {
          335                     ui.horizontal(|ui| {
          336                         ui.label(fl!(crate::LANGUAGE_LOADER, "animation_editor_path_label"));
          337                         let mut path_edit = self.export_path.to_str().unwrap().to_string();
          338                         let response = ui.add(
          339                             //    ui.available_size(),
          340                             TextEdit::singleline(&mut path_edit).desired_width(f32::INFINITY),
          341                         );
          342                         if response.changed() {
          343                             self.export_path = path_edit.into();
          344                         }
          345                     });
          346                     ui.add_space(8.0);
          347                     ui.horizontal(|ui| {
          348                         for (i, enc) in ENCODERS.iter().enumerate() {
          349                             if ui.selectable_label(self.export_type == i, enc.label()).clicked() {
          350                                 self.export_type = i;
          351                                 self.export_path.set_extension(enc.extension());
          352                             }
          353                         }
          354 
          355                         if ui.button(fl!(crate::LANGUAGE_LOADER, "animation_editor_export_button")).clicked() {
          356                             if let Err(err) = self.export() {
          357                                 message = Some(Message::ShowError(format!("Could not export: {}", err)));
          358                             }
          359                         }
          360                     });
          361 
          362                     if !self.encoding_error.is_empty() {
          363                         ui.colored_label(ui.style().visuals.error_fg_color, RichText::new(&self.encoding_error));
          364                     } else {
          365                         ui.horizontal(|ui| {
          366                             ui.small(fl!(crate::LANGUAGE_LOADER, "animation_icy_play_note"));
          367                             ui.hyperlink_to(RichText::new("Icy Play").small(), "https://github.com/mkrueger/icy_play");
          368                         });
          369                     }
          370                 }
          371             });
          372 
          373         egui::CentralPanel::default().show_inside(ui, |ui| {
          374             TopBottomPanel::bottom("code_error_bottom_panel").exact_height(200.).show_inside(ui, |ui| {
          375                 if !self.animator.lock().unwrap().error.is_empty() {
          376                     ui.colored_label(ui.style().visuals.error_fg_color, RichText::new(&self.animator.lock().unwrap().error).small());
          377                 } else {
          378                     egui::ScrollArea::vertical().max_width(f32::INFINITY).show(ui, |ui| {
          379                         self.animator.lock().unwrap().log.iter().for_each(|line| {
          380                             ui.horizontal(|ui| {
          381                                 ui.label(RichText::new(format!("Frame {}:", line.frame)).strong());
          382                                 ui.label(RichText::new(&line.text));
          383                                 ui.add_space(ui.available_width());
          384                             });
          385                         });
          386                     });
          387                 }
          388             });
          389 
          390             let r = CodeEditor::default()
          391                 .id_source("code editor")
          392                 .with_rows(12)
          393                 .with_fontsize(14.0)
          394                 .with_theme(if ui.style().visuals.dark_mode {
          395                     egui_code_editor::ColorTheme::GITHUB_DARK
          396                 } else {
          397                     egui_code_editor::ColorTheme::GITHUB_LIGHT
          398                 })
          399                 .with_syntax(highlighting::lua())
          400                 .with_numlines(true)
          401                 .show(ui, &mut self.txt);
          402             if self.shedule_update && self.last_update.elapsed().as_millis() > 1000 {
          403                 self.shedule_update = false;
          404 
          405                 let path = self.parent_path.clone();
          406                 let txt = self.txt.clone();
          407                 self.set_frame = self.animator.lock().unwrap().get_cur_frame();
          408                 self.next_animator = Some(Animator::run(&path, txt));
          409             }
          410 
          411             if let Some(range) = r.cursor_range {
          412                 if let Some((i, _)) = self.txt.char_indices().nth(range.as_sorted_char_range().start) {
          413                     self.cursor_index = i;
          414                 } else {
          415                     self.cursor_index = 0;
          416                 }
          417             }
          418             if r.response.changed {
          419                 self.shedule_update = true;
          420                 self.last_update = Instant::now();
          421                 self.undostack += 1;
          422             }
          423         });
          424 
          425         let buffer_view = self.buffer_view.clone();
          426         if self.animator.lock().unwrap().success() {
          427             self.animator.lock().unwrap().update_frame(buffer_view);
          428         }
          429         message
          430     }
          431 
          432     fn get_bytes(&mut self, _path: &Path) -> TerminalResult<Vec<u8>> {
          433         Ok(self.txt.as_bytes().to_vec())
          434     }
          435 
          436     fn get_ansi_editor_mut(&mut self) -> Option<&mut AnsiEditor> {
          437         None
          438     }
          439 
          440     fn get_ansi_editor(&self) -> Option<&AnsiEditor> {
          441         None
          442     }
          443 
          444     fn destroy(&self, gl: &glow::Context) -> Option<Message> {
          445         self.buffer_view.lock().destroy(gl);
          446         None
          447     }
          448 }