document_docking.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
       ---
       document_docking.rs (15189B)
       ---
            1 use std::{fs, path::PathBuf, sync::Arc, time::Instant};
            2 
            3 use crate::{
            4     create_image,
            5     model::Tool,
            6     util::autosave::{remove_autosave, store_auto_save},
            7     Document, DocumentOptions, Message, Settings, DEFAULT_CHAR_SET_TABLE, FIRST_TOOL, MRU_FILES,
            8 };
            9 use eframe::{
           10     egui::{self, Response, Ui},
           11     epaint::Rgba,
           12 };
           13 use egui::{mutex::Mutex, Sense, TextureHandle, Widget};
           14 use egui_tiles::{Tabs, TileId, Tiles};
           15 use i18n_embed_fl::fl;
           16 use icy_engine::{AttributedChar, Buffer, TextAttribute, TextPane};
           17 
           18 pub struct DocumentTab {
           19     full_path: Option<PathBuf>,
           20     pub doc: Arc<Mutex<Box<dyn Document>>>,
           21     last_save: usize,
           22 
           23     // autosave variables
           24     auto_save_status: usize,
           25     instant: Instant,
           26     last_change_autosave_timer: usize,
           27     destroyed: bool,
           28 }
           29 impl DocumentTab {
           30     pub fn is_dirty(&self) -> bool {
           31         let undo_stack_len = self.doc.lock().undo_stack_len();
           32         self.last_save != undo_stack_len
           33     }
           34 
           35     pub(crate) fn save(&mut self) -> Option<Message> {
           36         let Some(path) = &self.full_path else {
           37             log::error!("No path to save to");
           38             return None;
           39         };
           40         let doc = &mut self.doc.lock();
           41         unsafe { MRU_FILES.add_recent_file(path) };
           42 
           43         let mut msg = None;
           44         match doc.get_bytes(path) {
           45             Ok(bytes) => {
           46                 let mut tmp_file = path.clone();
           47                 let ext = path.extension().unwrap_or_default().to_str().unwrap_or_default().to_ascii_lowercase();
           48 
           49                 tmp_file.with_extension(format!("{}~", ext));
           50                 let mut num = 1;
           51                 while tmp_file.exists() {
           52                     tmp_file = tmp_file.with_extension(format!("{}{}~", ext, num));
           53                     num += 1;
           54                 }
           55 
           56                 if let Err(err) = fs::write(&tmp_file, bytes) {
           57                     msg = Some(Message::ShowError(format!("Error writing file {err}")));
           58                 } else if let Err(err) = fs::rename(tmp_file, path) {
           59                     msg = Some(Message::ShowError(format!("Error moving file {err}")));
           60                 }
           61                 remove_autosave(path);
           62 
           63                 let undo_stack_len = doc.undo_stack_len();
           64                 self.last_save = undo_stack_len;
           65                 self.last_change_autosave_timer = undo_stack_len;
           66                 self.auto_save_status = undo_stack_len;
           67                 doc.inform_save();
           68             }
           69             Err(err) => {
           70                 msg = Some(Message::ShowError(format!("{err}")));
           71             }
           72         }
           73         if msg.is_none() {
           74             remove_autosave(path);
           75         }
           76         msg
           77     }
           78 
           79     pub fn get_path(&self) -> Option<PathBuf> {
           80         self.full_path.clone()
           81     }
           82 
           83     pub fn set_path(&mut self, mut path: PathBuf) {
           84         let doc = &mut self.doc.lock();
           85         path.set_extension(doc.default_extension());
           86         if let Some(old_path) = &self.full_path {
           87             remove_autosave(old_path);
           88         }
           89         self.full_path = Some(path);
           90     }
           91 
           92     pub fn is_untitled(&self) -> bool {
           93         self.full_path.is_none()
           94     }
           95 
           96     pub fn is_destroyed(&self) -> bool {
           97         self.destroyed
           98     }
           99 
          100     pub fn destroy(&mut self, gl: &glow::Context) -> Option<Message> {
          101         if self.destroyed {
          102             return None;
          103         }
          104         self.destroyed = true;
          105         self.doc.lock().destroy(gl)
          106     }
          107 }
          108 
          109 pub struct DocumentBehavior {
          110     pub tools: Arc<Mutex<Vec<Box<dyn Tool>>>>,
          111     selected_tool: usize,
          112     prev_tool: usize,
          113     pub document_options: DocumentOptions,
          114 
          115     char_set_img: Option<TextureHandle>,
          116     cur_char_set: usize,
          117     dark_mode: bool,
          118 
          119     pos_img: Option<TextureHandle>,
          120     cur_line_col_txt: String,
          121 
          122     pub request_close: Option<TileId>,
          123     pub request_close_others: Option<TileId>,
          124     pub request_close_all: Option<TileId>,
          125 
          126     pub message: Option<Message>,
          127 }
          128 
          129 impl DocumentBehavior {
          130     pub fn new(tools: Arc<Mutex<Vec<Box<dyn Tool>>>>) -> Self {
          131         Self {
          132             tools,
          133             selected_tool: FIRST_TOOL,
          134             prev_tool: FIRST_TOOL,
          135             document_options: DocumentOptions::default(),
          136             char_set_img: None,
          137             cur_char_set: usize::MAX,
          138             request_close: None,
          139             request_close_others: None,
          140             request_close_all: None,
          141             message: None,
          142             pos_img: None,
          143             cur_line_col_txt: String::new(),
          144             dark_mode: true,
          145         }
          146     }
          147 
          148     pub fn get_selected_tool(&self) -> usize {
          149         self.selected_tool
          150     }
          151 
          152     pub(crate) fn set_selected_tool(&mut self, tool: usize) {
          153         if self.selected_tool == tool {
          154             return;
          155         }
          156         self.prev_tool = self.selected_tool;
          157         self.selected_tool = tool;
          158     }
          159 
          160     pub(crate) fn select_prev_tool(&mut self) {
          161         self.selected_tool = self.prev_tool;
          162     }
          163 }
          164 
          165 impl egui_tiles::Behavior<DocumentTab> for DocumentBehavior {
          166     fn tab_title_for_pane(&mut self, pane: &DocumentTab) -> egui::WidgetText {
          167         let mut title = if let Some(file_name) = &pane.full_path {
          168             file_name.file_name().unwrap_or_default().to_str().unwrap_or_default().to_string()
          169         } else {
          170             fl!(crate::LANGUAGE_LOADER, "unsaved-title")
          171         };
          172         if pane.is_dirty() {
          173             title.push('*');
          174         }
          175         title.into()
          176     }
          177 
          178     fn pane_ui(&mut self, ui: &mut egui::Ui, _tile_id: egui_tiles::TileId, pane: &mut DocumentTab) -> egui_tiles::UiResponse {
          179         if pane.is_destroyed() {
          180             return egui_tiles::UiResponse::None;
          181         }
          182 
          183         let doc = &mut pane.doc.lock();
          184         self.message = doc.show_ui(ui, &mut self.tools.lock()[self.selected_tool], self.selected_tool, &self.document_options);
          185 
          186         let undo_stack_len = doc.undo_stack_len();
          187         if let Some(path) = &pane.full_path {
          188             if undo_stack_len != pane.auto_save_status {
          189                 if pane.last_change_autosave_timer != undo_stack_len {
          190                     pane.instant = Instant::now();
          191                 }
          192                 pane.last_change_autosave_timer = undo_stack_len;
          193 
          194                 if pane.instant.elapsed().as_secs() > 5 {
          195                     pane.auto_save_status = undo_stack_len;
          196                     if let Ok(bytes) = doc.get_bytes(path) {
          197                         store_auto_save(path, &bytes);
          198                     }
          199                 }
          200             }
          201         }
          202 
          203         egui_tiles::UiResponse::None
          204     }
          205 
          206     fn on_tab_button(&mut self, tiles: &Tiles<DocumentTab>, tile_id: TileId, button_response: eframe::egui::Response) -> Response {
          207         let response_opt = button_response.context_menu(|ui| {
          208             if ui.button(fl!(crate::LANGUAGE_LOADER, "tab-context-menu-close")).clicked() {
          209                 self.on_close_requested(tiles, tile_id);
          210                 ui.close_menu();
          211             }
          212             if ui.button(fl!(crate::LANGUAGE_LOADER, "tab-context-menu-close_others")).clicked() {
          213                 self.request_close_others = Some(tile_id);
          214                 ui.close_menu();
          215             }
          216             if ui.button(fl!(crate::LANGUAGE_LOADER, "tab-context-menu-close_all")).clicked() {
          217                 self.request_close_all = Some(tile_id);
          218                 ui.close_menu();
          219             }
          220             ui.separator();
          221             if ui.button(fl!(crate::LANGUAGE_LOADER, "tab-context-menu-copy_path")).clicked() {
          222                 if let Some(egui_tiles::Tile::Pane(pane)) = tiles.get(tile_id) {
          223                     if let Some(path) = &pane.full_path {
          224                         let text = path.to_string_lossy().to_string();
          225                         ui.output_mut(|o| o.copied_text = text);
          226                     }
          227                 }
          228                 ui.close_menu();
          229             }
          230         });
          231         if let Some(response_opt) = response_opt {
          232             response_opt.response
          233         } else {
          234             button_response
          235         }
          236     }
          237 
          238     fn on_close_requested(&mut self, _tiles: &Tiles<DocumentTab>, tile_id: TileId) {
          239         self.request_close = Some(tile_id);
          240     }
          241 
          242     fn simplification_options(&self) -> egui_tiles::SimplificationOptions {
          243         egui_tiles::SimplificationOptions {
          244             all_panes_must_have_tabs: true,
          245             ..Default::default()
          246         }
          247     }
          248 
          249     fn has_close_buttons(&self) -> bool {
          250         true
          251     }
          252 
          253     fn top_bar_right_ui(&mut self, tiles: &Tiles<DocumentTab>, ui: &mut Ui, _tile_id: TileId, tabs: &Tabs, _scroll_offset: &mut f32) {
          254         if let Some(id) = tabs.active {
          255             if let Some(egui_tiles::Tile::Pane(pane)) = tiles.get(id) {
          256                 let doc = &mut pane.doc.lock();
          257                 if let Some(editor) = doc.get_ansi_editor() {
          258                     ui.add_space(4.0);
          259                     let mut buffer = Buffer::new((48, 1));
          260                     let font_page = editor.buffer_view.lock().get_caret().get_font_page();
          261                     if let Some(font) = editor.buffer_view.lock().get_buffer().get_font(font_page) {
          262                         buffer.set_font(1, font.clone());
          263                     }
          264 
          265                     let char_set = Settings::get_character_set();
          266                     if self.cur_char_set != char_set || self.dark_mode != ui.style().visuals.dark_mode {
          267                         let c = if ui.style().visuals.dark_mode {
          268                             ui.style().visuals.extreme_bg_color
          269                         } else {
          270                             (Rgba::from(ui.style().visuals.panel_fill) * Rgba::from_gray(0.8)).into()
          271                         };
          272 
          273                         let bg_color = buffer.palette.insert_color_rgb(c.r(), c.g(), c.b());
          274 
          275                         let c = ui.style().visuals.strong_text_color();
          276                         let fg_color = buffer.palette.insert_color_rgb(c.r(), c.g(), c.b());
          277 
          278                         let mut attr: TextAttribute = TextAttribute::default();
          279                         attr.set_background(bg_color);
          280                         attr.set_foreground(fg_color);
          281                         let s = format!("Set {:2} ", char_set + 1);
          282                         let mut i = 0;
          283                         for c in s.chars() {
          284                             buffer.layers[0].set_char((i, 0), AttributedChar::new(c, attr));
          285                             i += 1;
          286                         }
          287                         attr.set_foreground(15);
          288                         attr.set_background(4);
          289 
          290                         for j in i..buffer.get_width() {
          291                             buffer.layers[0].set_char((j, 0), AttributedChar::new(' ', attr));
          292                         }
          293 
          294                         for j in 0..10 {
          295                             if j == 9 {
          296                                 i += 1;
          297                             }
          298                             let s = format!("{:-2}=", j + 1);
          299                             attr.set_foreground(0);
          300                             for c in s.chars() {
          301                                 buffer.layers[0].set_char((i, 0), AttributedChar::new(c, attr));
          302                                 i += 1;
          303                             }
          304                             attr.set_foreground(15);
          305                             attr.set_font_page(1);
          306                             buffer.layers[0].set_char((i, 0), AttributedChar::new(editor.get_char_set_key(j), attr));
          307                             attr.set_font_page(0);
          308                             i += 1;
          309                         }
          310 
          311                         self.char_set_img = Some(create_image(ui.ctx(), &buffer));
          312                     }
          313 
          314                     if let Some(handle) = &self.char_set_img {
          315                         let mut img = egui::Image::from_texture(handle);
          316                         img = img.sense(Sense::click());
          317                         let res = img.ui(ui);
          318                         if res.clicked_by(egui::PointerButton::Primary) {
          319                             Settings::set_character_set((char_set + 1) % DEFAULT_CHAR_SET_TABLE.len())
          320                         } else if res.clicked_by(egui::PointerButton::Secondary) {
          321                             Settings::set_character_set((char_set + DEFAULT_CHAR_SET_TABLE.len() - 1) % DEFAULT_CHAR_SET_TABLE.len())
          322                         }
          323                     }
          324 
          325                     let txt = self.tools.lock()[self.selected_tool].get_toolbar_location_text(editor);
          326                     if txt != self.cur_line_col_txt || self.dark_mode != ui.style().visuals.dark_mode {
          327                         self.cur_line_col_txt = txt;
          328                         self.dark_mode = ui.style().visuals.dark_mode;
          329                         let mut txt2 = String::new();
          330                         let mut char_count = 0;
          331                         for c in self.cur_line_col_txt.chars() {
          332                             if (c as u32) < 255 {
          333                                 txt2.push(c);
          334                                 char_count += 1;
          335                             }
          336                         }
          337 
          338                         let mut buffer = Buffer::new((char_count, 1));
          339                         buffer.is_terminal_buffer = false;
          340                         let mut attr: TextAttribute = TextAttribute::default();
          341                         let c = if ui.style().visuals.dark_mode {
          342                             ui.style().visuals.extreme_bg_color
          343                         } else {
          344                             (Rgba::from(ui.style().visuals.panel_fill) * Rgba::from_gray(0.8)).into()
          345                         };
          346 
          347                         let bg_color = buffer.palette.insert_color_rgb(c.r(), c.g(), c.b());
          348                         attr.set_background(bg_color);
          349 
          350                         let c = ui.style().visuals.text_color();
          351                         let fg_color = buffer.palette.insert_color_rgb(c.r(), c.g(), c.b());
          352                         attr.set_foreground(fg_color);
          353 
          354                         for (i, mut c) in txt2.chars().enumerate() {
          355                             if c as u32 > 255 {
          356                                 c = ' ';
          357                             }
          358                             buffer.layers[0].set_char((i, 0), AttributedChar::new(c, attr));
          359                         }
          360                         self.pos_img = Some(create_image(ui.ctx(), &buffer));
          361                     }
          362 
          363                     if let Some(img) = &self.pos_img {
          364                         egui::Image::from_texture(img).ui(ui);
          365                     }
          366                 }
          367             }
          368         }
          369     }
          370 }
          371 
          372 pub fn add_child(tree: &mut egui_tiles::Tree<DocumentTab>, full_path: Option<PathBuf>, doc: Box<dyn Document>) {
          373     let tile = DocumentTab {
          374         full_path,
          375         doc: Arc::new(Mutex::new(doc)),
          376         auto_save_status: 0,
          377         last_save: 0,
          378         instant: Instant::now(),
          379         last_change_autosave_timer: 0,
          380         destroyed: false,
          381     };
          382     let new_child = tree.tiles.insert_pane(tile);
          383 
          384     if let Some(root) = tree.root {
          385         if let Some(egui_tiles::Tile::Container(egui_tiles::Container::Tabs(tabs))) = tree.tiles.get_mut(root) {
          386             tabs.add_child(new_child);
          387             tabs.set_active(new_child);
          388         } else if let Some(egui_tiles::Tile::Pane(_)) = tree.tiles.get(root) {
          389             let new_id = tree.tiles.insert_tab_tile(vec![new_child, root]);
          390             tree.root = Some(new_id);
          391         } else {
          392             tree.root = Some(new_child);
          393         }
          394     } else {
          395         tree.root = Some(new_child);
          396     }
          397 }