mod.rs - icy_draw - [fork] icy_draw is the successor to mystic draw.
HTML git clone https://git.drkhsh.at/icy_draw.git
DIR Log
DIR Files
DIR Refs
DIR README
---
mod.rs (26119B)
---
1 use std::{path::Path, sync::Arc};
2
3 use eframe::{
4 egui::{self, Button, ScrollArea, SidePanel, TextEdit, TopBottomPanel},
5 epaint::{mutex::Mutex, Vec2},
6 };
7 use egui::{load::SizedTexture, Image, Rect, TextureHandle};
8 use i18n_embed_fl::fl;
9 use icy_engine::{AttributedChar, BitFont, Buffer, EngineResult, FontGlyph, Layer, Size, TextAttribute, TextPane, TheDrawFont};
10 use icy_engine_gui::{show_terminal_area, BufferView};
11
12 use crate::{
13 model::{click_imp::VALID_OUTLINE_CHARS, Tool},
14 AnsiEditor, BitFontEditor, ClipboardHandler, Document, DocumentOptions, DrawGlyphStyle, Message, SelectOutlineDialog, TerminalResult, UndoHandler,
15 SETTINGS,
16 };
17
18 pub struct CharFontEditor {
19 id: usize,
20 font: BitFont,
21 selected_char_opt: Option<char>,
22 old_selected_char_opt: Option<char>,
23
24 outline_previewbuffer_view: Arc<Mutex<BufferView>>,
25
26 ansi_editor: AnsiEditor,
27 selected_font: usize,
28 fonts: Vec<TheDrawFont>,
29 undostack_len: usize,
30 last_update_preview: usize,
31 last_update_preview_attr: TextAttribute,
32 outline_selection: crate::SelectOutlineDialog,
33 draw_outline_bg: bool,
34 opt_cheat_sheet: Option<TextureHandle>,
35 tool_switch: bool,
36 }
37
38 impl ClipboardHandler for CharFontEditor {
39 fn can_cut(&self) -> bool {
40 self.ansi_editor.can_cut()
41 }
42 fn cut(&mut self) -> EngineResult<()> {
43 self.ansi_editor.cut()
44 }
45
46 fn can_copy(&self) -> bool {
47 self.ansi_editor.can_copy()
48 }
49
50 fn copy(&mut self) -> EngineResult<()> {
51 self.ansi_editor.copy()
52 }
53
54 fn can_paste(&self) -> bool {
55 self.ansi_editor.can_paste()
56 }
57
58 fn paste(&mut self) -> EngineResult<()> {
59 self.ansi_editor.paste()
60 }
61 }
62
63 impl UndoHandler for CharFontEditor {
64 fn undo_description(&self) -> Option<String> {
65 self.ansi_editor.undo_description()
66 }
67
68 fn can_undo(&self) -> bool {
69 self.ansi_editor.can_undo()
70 }
71
72 fn undo(&mut self) -> EngineResult<Option<Message>> {
73 self.ansi_editor.undo()?;
74 Ok(None)
75 }
76
77 fn redo_description(&self) -> Option<String> {
78 self.ansi_editor.redo_description()
79 }
80
81 fn can_redo(&self) -> bool {
82 self.ansi_editor.can_redo()
83 }
84
85 fn redo(&mut self) -> EngineResult<Option<Message>> {
86 self.ansi_editor.redo()?;
87 Ok(None)
88 }
89 }
90
91 impl Document for CharFontEditor {
92 fn default_extension(&self) -> &'static str {
93 "tdf"
94 }
95 fn has_tool_switched(&self) -> bool {
96 self.tool_switch
97 }
98 fn set_tool_switch(&mut self, switched: bool) {
99 self.tool_switch = switched;
100 }
101
102 fn undo_stack_len(&self) -> usize {
103 self.undostack_len
104 }
105
106 fn get_bytes(&mut self, _path: &Path) -> TerminalResult<Vec<u8>> {
107 self.undostack_len += 1;
108 self.save_old_selected_char();
109 TheDrawFont::create_font_bundle(&self.fonts)
110 }
111
112 fn show_ui(&mut self, ui: &mut egui::Ui, cur_tool: &mut Box<dyn Tool>, selected_tool: usize, options: &DocumentOptions) -> Option<Message> {
113 SidePanel::left("side_panel").default_width(200.0).show_inside(ui, |ui| {
114 ui.add_space(4.0);
115
116 if self.selected_font < self.fonts.len() {
117 ScrollArea::vertical().show(ui, |ui| {
118 ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
119
120 for i in 0..self.fonts.len() {
121 if ui.selectable_value(&mut self.selected_font, i, &self.fonts[i].name).clicked() {
122 self.save_old_selected_char();
123 self.selected_font = i;
124 self.old_selected_char_opt = None;
125 self.selected_char_opt = None;
126 self.show_selected_char();
127 }
128 }
129 });
130 }
131 ui.separator();
132
133 ui.horizontal(|ui| {
134 /*if ui.button("+").clicked() {
135 self.fonts.push(TheDrawFont::new(
136 "New Font",
137 icy_engine::FontType::Color,
138 1,
139 ));
140 self.selected_font = self.fonts.len() - 1;
141 self.selected_char_opt = None;
142 self.old_selected_char_opt = None;
143 self.show_selected_char();
144 self.undostack_len += 1;
145 }*/
146
147 if ui.add_enabled(self.fonts.len() > 1, Button::new("🗑")).clicked() {
148 self.fonts.remove(self.selected_font);
149 self.selected_font = 0;
150 self.selected_char_opt = None;
151 self.old_selected_char_opt = None;
152 self.show_selected_char();
153 self.undostack_len += 1;
154 }
155
156 if ui.button(fl!(crate::LANGUAGE_LOADER, "tdf-editor-clone_button")).clicked() {
157 self.fonts.push(self.fonts[self.selected_font].clone());
158 self.selected_font = self.fonts.len() - 1;
159 self.selected_char_opt = None;
160 self.old_selected_char_opt = None;
161 self.show_selected_char();
162 self.undostack_len += 1;
163 }
164 });
165 });
166
167 TopBottomPanel::top("char_top_panel").exact_height(60.).show_inside(ui, |ui| {
168 ui.add_space(4.0);
169 if self.selected_font < self.fonts.len() {
170 egui::Grid::new(
171 "font_grid
172 ",
173 )
174 .num_columns(4)
175 .spacing([4.0, 4.0])
176 .show(ui, |ui| {
177 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
178 ui.label(fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_name_label"));
179 });
180 if ui
181 .add(
182 TextEdit::singleline(&mut self.fonts[self.selected_font].name)
183 .min_size(Vec2::new(200.0, 22.))
184 .char_limit(12),
185 )
186 .changed()
187 {
188 self.undostack_len += 1;
189 }
190
191 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
192 ui.label(fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_type_label"));
193 });
194
195 let text = match self.fonts[self.selected_font].font_type {
196 icy_engine::FontType::Outline => {
197 fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_type_outline")
198 }
199 icy_engine::FontType::Block => {
200 fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_type_block")
201 }
202 icy_engine::FontType::Color => {
203 fl!(crate::LANGUAGE_LOADER, "tdf-editor-font_type_color")
204 }
205 };
206 ui.label(text);
207
208 ui.end_row();
209 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
210 ui.label(fl!(crate::LANGUAGE_LOADER, "tdf-editor-spacing_label"));
211 });
212 if ui
213 .add(egui::DragValue::new(&mut self.fonts[self.selected_font].spaces).range(0.0..=40.0))
214 .changed()
215 {
216 self.undostack_len += 1;
217 }
218 ui.label("");
219 ui.label("");
220 ui.end_row();
221 });
222 } else {
223 ui.heading(fl!(crate::LANGUAGE_LOADER, "tdf-editor-no_font_selected_label"));
224 }
225 });
226
227 TopBottomPanel::bottom("char_bottom_panel").exact_height(150.).show_inside(ui, |ui| {
228 if self.selected_font < self.fonts.len() {
229 self.show_char_selector(ui);
230 ui.add_space(4.0);
231 if self.selected_char_opt.is_some() && ui.button(fl!(crate::LANGUAGE_LOADER, "tdf-editor-clear_char_button")).clicked() {
232 self.fonts[self.selected_font].clear_glyph(self.selected_char_opt.unwrap());
233 self.selected_char_opt = None;
234 self.old_selected_char_opt = None;
235 self.show_selected_char();
236 self.undostack_len += 1;
237 }
238 }
239 });
240
241 egui::CentralPanel::default().show_inside(ui, |ui| {
242 if self.selected_font < self.fonts.len() {
243 let attr = self
244 .ansi_editor
245 .buffer_view
246 .lock()
247 .get_edit_state()
248 .get_caret()
249 .get_attribute();
250
251 let mut is_outline = false;
252 for layer in &mut self
253 .ansi_editor
254 .buffer_view
255 .lock()
256 .get_edit_state_mut()
257 .get_buffer_mut()
258 .layers
259 {
260 match self.fonts[self.selected_font].font_type {
261 icy_engine::FontType::Outline => {
262 is_outline = true;
263 set_attribute(layer, attr);
264 }
265 icy_engine::FontType::Block => {
266 set_attribute(layer, attr);
267 }
268 icy_engine::FontType::Color => {
269 }
270 }
271 }
272
273 if is_outline {
274 SidePanel::right("outline…_side_panel")
275 .default_width(290.)
276 .show_inside(ui, |ui| {
277 TopBottomPanel::bottom("outline_style_bottom_panel")
278 .exact_height(220.)
279 .show_inside(ui, |ui| {
280 self.outline_selection.show_outline_ui(ui, 8, Vec2::new(4.0, 4.0));
281 let outline_style = self.outline_selection.get_outline_style();
282 let old_style = self.outline_previewbuffer_view.lock().get_edit_state_mut().get_outline_style();
283 self.outline_previewbuffer_view.lock().get_edit_state_mut().set_outline_style(outline_style);
284 if outline_style != old_style {
285 self.show_selected_char();
286 }
287 });
288
289 let opt = icy_engine_gui::TerminalOptions {
290 stick_to_bottom: false,
291 scale: Some(Vec2::new(2.0, 2.0)),
292 monitor_settings: unsafe { SETTINGS.monitor_settings.clone() },
293 marker_settings: unsafe { SETTINGS.marker_settings.clone() },
294 id: Some(egui::Id::new(self.id + 20000)),
295 ..Default::default()
296 };
297
298 self.outline_previewbuffer_view
299 .lock()
300 .get_caret_mut()
301 .set_is_visible(false);
302 ui.horizontal(|ui| {
303 ui.label(fl!(crate::LANGUAGE_LOADER, "tdf-editor-outline_preview_label"));
304 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
305 if ui.checkbox(&mut self.draw_outline_bg, fl!(crate::LANGUAGE_LOADER, "tdf-editor-draw_bg_checkbox")).changed() {
306 self.render_outline_preview();
307 }
308 });
309 });
310 let (_, _) = show_terminal_area(
311 ui,
312 self.outline_previewbuffer_view.clone(),
313 opt,
314 );
315 });
316
317
318 TopBottomPanel::top("cheat_sheet_top_panel")
319 .exact_height(50.)
320 .show_inside(ui, |ui| {
321 if self.opt_cheat_sheet.is_none() {
322
323 let mut key = fl!(crate::LANGUAGE_LOADER, "tdf-editor-cheat_sheet_key");
324 let mut code = fl!(crate::LANGUAGE_LOADER, "tdf-editor-cheat_sheet_code");
325 let mut res = fl!(crate::LANGUAGE_LOADER, "tdf-editor-cheat_sheet_res");
326
327 let m = key.len().max(code.len()).max(res.len());
328 let mut buffer = Buffer::new((56 + m, 3));
329 while key.len() < m {
330 key.insert(0, ' ');
331 }
332 while code.len() < m {
333 code.insert(0, ' ');
334 }
335 while res.len() < m {
336 res.insert(0, ' ');
337 }
338
339 let s = format!("{key}: F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 1 2 3 4 5 6 7 8 ");
340 let s2 = format!("{code}: A B C D E F G H I J K L M N O @ & \u{F7} ");
341 let s3 = format!("{res}: \u{CD} \u{C4} \u{B3} \u{BA} \u{D5} \u{BB} \u{D5} \u{BF} \u{C8} \u{BE} \u{C0} \u{BD} \u{B5} \u{C7} SP & \u{F7}");
342
343 let mut attr = TextAttribute::default();
344 attr.set_foreground(0);
345 attr.set_background(4);
346
347 for (i, c) in s.chars().enumerate() {
348 buffer.layers[0].set_char((i, 0), AttributedChar::new(c, attr));
349 }
350
351 attr.set_foreground(15);
352 attr.set_background(4);
353
354 for (i, c) in s2.chars().enumerate() {
355 buffer.layers[0].set_char((i, 1), AttributedChar::new(c, attr));
356 }
357 attr.set_foreground(14);
358 attr.set_background(0);
359
360 for (i, c) in s3.chars().enumerate() {
361 buffer.layers[0].set_char((i, 2), AttributedChar::new(c, attr));
362 }
363 self.opt_cheat_sheet = Some(crate::create_image(ui.ctx(),&buffer));
364 }
365
366 if let Some(image) = & self.opt_cheat_sheet {
367 ui.vertical_centered(|ui| {
368 let sized_texture:SizedTexture = (image).into();
369 let image = Image::from_texture(sized_texture);
370 let mut size = sized_texture.size;
371 let width = ui.available_width();
372 if width < size.x {
373 size.y *= width / size.x;
374 size.x = width;
375 }
376 let r = Rect::from_min_size(
377 ui.min_rect().min,
378 size,
379 );
380 image.paint_at(ui, r);
381
382 });
383 }
384 });
385
386 egui::CentralPanel::default().show_inside(ui, |ui| {
387 self.ansi_editor
388 .show_ui(ui, cur_tool, selected_tool, options);
389 });
390 } else {
391 self.ansi_editor
392 .show_ui(ui, cur_tool, selected_tool, options);
393 }
394 }
395 });
396 let u = self.ansi_editor.buffer_view.lock().get_edit_state().undo_stack_len();
397 let attr = self.ansi_editor.buffer_view.lock().get_edit_state().get_caret().get_attribute();
398 if self.last_update_preview != u || self.last_update_preview_attr != attr {
399 self.last_update_preview = u;
400 self.last_update_preview_attr = attr;
401 self.save_old_selected_char();
402 self.render_outline_preview();
403 }
404
405 None
406 }
407
408 fn get_ansi_editor_mut(&mut self) -> Option<&mut AnsiEditor> {
409 self.ansi_editor.get_ansi_editor_mut()
410 }
411
412 fn get_ansi_editor(&self) -> Option<&AnsiEditor> {
413 self.ansi_editor.get_ansi_editor()
414 }
415
416 fn destroy(&self, gl: &glow::Context) -> Option<Message> {
417 self.ansi_editor.destroy(gl);
418 None
419 }
420 }
421
422 fn set_attribute(layer: &mut Layer, attr: TextAttribute) {
423 for y in 0..layer.get_size().height {
424 for x in 0..layer.get_size().width {
425 let mut c = layer.get_char((x, y));
426 if !c.is_visible() {
427 continue;
428 }
429 c.attribute = attr;
430 layer.set_char((x, y), c);
431 }
432 }
433 }
434
435 impl CharFontEditor {
436 pub fn new(gl: &Arc<glow::Context>, id: usize, fonts: Vec<TheDrawFont>) -> Self {
437 let mut buffer = Buffer::new(Size::new(30, 12));
438 set_up_layers(&mut buffer);
439 let ansi_editor = AnsiEditor::new(gl, id, buffer);
440
441 let mut buffer = Buffer::new(Size::new(30, 12));
442 buffer.is_terminal_buffer = false;
443 let mut buffer_view = BufferView::from_buffer(gl, buffer);
444 buffer_view.interactive = false;
445 let outline_previewbuffer_view = Arc::new(Mutex::new(buffer_view));
446
447 let mut res = Self {
448 id,
449 font: BitFont::default(),
450 ansi_editor,
451 selected_char_opt: Some('A'),
452 old_selected_char_opt: None,
453 fonts,
454 selected_font: 0,
455 undostack_len: 0,
456 outline_previewbuffer_view,
457 outline_selection: SelectOutlineDialog::default(),
458 last_update_preview: 0,
459 opt_cheat_sheet: None,
460 draw_outline_bg: true,
461 last_update_preview_attr: TextAttribute::default(),
462 tool_switch: true,
463 };
464 res.show_selected_char();
465 res
466 }
467
468 pub fn show_char_selector(&mut self, ui: &mut egui::Ui) {
469 egui::ScrollArea::vertical().show(ui, |ui| {
470 ui.horizontal_wrapped(|ui| {
471 ui.spacing_mut().item_spacing = egui::Vec2::new(0., 0.);
472 for i in b'!'..=b'~' {
473 let ch = unsafe { char::from_u32_unchecked(i as u32) };
474 let mut style = DrawGlyphStyle::Normal;
475 if !self.fonts[self.selected_font].has_char(i) {
476 style = DrawGlyphStyle::GrayOut
477 }
478 if let Some(ch2) = self.selected_char_opt {
479 if ch == ch2 {
480 style = DrawGlyphStyle::Selected
481 }
482 }
483 let response = BitFontEditor::draw_glyph(ui, &self.font, style, ch);
484 if response.clicked() {
485 self.selected_char_opt = Some(ch);
486 self.show_selected_char();
487 }
488 }
489 });
490 });
491 }
492
493 fn render_outline_preview(&mut self) {
494 let font = &self.fonts[self.selected_font];
495 if matches!(font.font_type, icy_engine::FontType::Outline) {
496 let lock = &mut self.ansi_editor.buffer_view.lock();
497 let mut attr = lock.get_caret().get_attribute();
498
499 let _ = self.outline_previewbuffer_view.lock().get_edit_state_mut().clear_layer(0);
500 self.outline_previewbuffer_view.lock().get_caret_mut().set_attr(attr);
501 if let Some(ch) = self.selected_char_opt {
502 let size = self.outline_previewbuffer_view.lock().get_edit_state_mut().get_buffer().get_size();
503
504 if self.draw_outline_bg {
505 attr.set_foreground(8);
506 attr.set_background(0);
507 for y in 0..size.height {
508 for x in 0..size.width {
509 self.outline_previewbuffer_view.lock().get_edit_state_mut().get_buffer_mut().layers[0]
510 .set_char((x, y), AttributedChar::new('\u{B1}', attr));
511 }
512 }
513 }
514
515 font.render(self.outline_previewbuffer_view.lock().get_edit_state_mut(), ch as u8);
516 }
517 }
518 }
519
520 fn show_selected_char(&mut self) {
521 {
522 self.save_old_selected_char();
523 let font = &self.fonts[self.selected_font];
524 self.ansi_editor.outline_font_mode = matches!(font.font_type, icy_engine::FontType::Outline);
525 let lock = &mut self.ansi_editor.buffer_view.lock();
526
527 let edit_state = &mut lock.get_edit_state_mut();
528 set_up_layers(edit_state.get_buffer_mut());
529 edit_state.set_current_layer(1);
530 edit_state.get_caret_mut().set_position((0, 0).into());
531 edit_state.set_outline_style(usize::MAX);
532
533 if let Some(ch) = self.selected_char_opt {
534 font.render(edit_state, ch as u8);
535 }
536
537 edit_state.get_undo_stack().lock().unwrap().clear();
538 self.old_selected_char_opt = self.selected_char_opt;
539 }
540 self.render_outline_preview();
541 }
542
543 fn save_old_selected_char(&mut self) {
544 if self.ansi_editor.buffer_view.lock().get_edit_state().undo_stack_len() == 0 {
545 return;
546 }
547 self.undostack_len += 1;
548 if let Some(font) = self.fonts.get_mut(self.selected_font) {
549 if let Some(ch) = self.old_selected_char_opt {
550 match font.font_type {
551 icy_engine::FontType::Outline => {
552 let lock = &mut self.ansi_editor.buffer_view.lock();
553 let buf = lock.get_buffer();
554 let mut data = Vec::new();
555 let mut w = 0;
556 let mut h = 0;
557 for y in 0..buf.get_line_count() {
558 if y > 0 {
559 data.push(13);
560 }
561 let lw = buf.get_line_length(y);
562 for x in 0..lw {
563 let ch = buf.get_char((x, y));
564 if VALID_OUTLINE_CHARS.contains(ch.ch) {
565 data.push(ch.ch as u8);
566 }
567 }
568 w = w.max(lw);
569 h = y;
570 }
571
572 font.set_glyph(ch, FontGlyph { size: Size::new(w, h), data });
573 }
574 icy_engine::FontType::Block => {
575 let lock = &mut self.ansi_editor.buffer_view.lock();
576 let buf = lock.get_buffer();
577 let mut data = Vec::new();
578 let mut w = 0;
579 let mut h = 0;
580 for y in 0..buf.get_line_count() {
581 if y > 0 {
582 data.push(13);
583 }
584 let lw = buf.get_line_length(y);
585 for x in 0..lw {
586 let ch = buf.get_char((x, y));
587 data.push(ch.ch as u8);
588 }
589 w = w.max(lw);
590 h = y;
591 }
592
593 font.set_glyph(ch, FontGlyph { size: Size::new(w, h), data });
594 }
595 icy_engine::FontType::Color => {
596 let lock = &mut self.ansi_editor.buffer_view.lock();
597 let buf = lock.get_buffer();
598 let mut data = Vec::new();
599 let mut w = 0;
600 let mut h = 0;
601 for y in 0..buf.get_line_count() {
602 if y > 0 {
603 data.push(13);
604 }
605 let lw = buf.get_line_length(y);
606 for x in 0..lw {
607 let ch = buf.get_char((x, y));
608 data.push(ch.ch as u8);
609 data.push(ch.attribute.as_u8(icy_engine::IceMode::Ice));
610 }
611 w = w.max(lw);
612 h = y;
613 }
614
615 font.set_glyph(ch, FontGlyph { size: Size::new(w, h), data });
616 }
617 }
618 }
619 }
620 }
621 }
622
623 fn set_up_layers(buffer: &mut Buffer) {
624 buffer.layers.clear();
625
626 let mut new_layer = Layer::new("background", Size::new(30, 12));
627 new_layer.properties.has_alpha_channel = false;
628 new_layer.properties.is_locked = true;
629 new_layer.properties.is_position_locked = true;
630 buffer.layers.push(new_layer);
631
632 let mut new_layer = Layer::new("edit layer", Size::new(30, 12));
633 new_layer.properties.has_alpha_channel = true;
634 new_layer.properties.is_position_locked = true;
635 buffer.layers.push(new_layer);
636 }