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