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 }