mod.rs - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
HTML git clone https://git.drkhsh.at/icy_draw.git
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
mod.rs (25439B)
---
1 mod undo;
2
3 use std::{path::Path, sync::Arc};
4
5 use eframe::{
6 egui::{self, Id, Layout, RichText, Sense},
7 emath::Align2,
8 epaint::{mutex::Mutex, Color32, FontFamily, FontId, Pos2, Rect, Rounding, Vec2},
9 };
10 use i18n_embed_fl::fl;
11 use icy_engine::{
12 util::{pop_data, push_data, BITFONT_GLYPH},
13 BitFont, Buffer, EngineResult, Glyph, Size, TextAttribute, TextPane,
14 };
15 use icy_engine_egui::{show_terminal_area, BufferView};
16
17 use crate::{model::Tool, to_message, AnsiEditor, ClipboardHandler, Document, DocumentOptions, Message, TerminalResult, UndoHandler, SETTINGS};
18
19 use self::undo::UndoOperation;
20
21 pub struct BitFontEditor {
22 id: usize,
23 original_font: BitFont,
24 last_updated_font: BitFont,
25 font: BitFont,
26
27 width: i32,
28 height: i32,
29
30 buffer_view: Arc<Mutex<BufferView>>,
31
32 selected_char_opt: Option<char>,
33 undo_stack: Arc<Mutex<Vec<Box<dyn UndoOperation>>>>,
34 redo_stack: Vec<Box<dyn UndoOperation>>,
35 old_data: Option<Vec<u8>>,
36
37 send_update_message: bool,
38 }
39
40 pub enum DrawGlyphStyle {
41 Normal,
42 Selected,
43 GrayOut,
44 }
45
46 impl BitFontEditor {
47 pub fn new(gl: &Arc<glow::Context>, id: usize, font: BitFont) -> Self {
48 let mut buffer = Buffer::new(Size::new(10, 10));
49 buffer.is_terminal_buffer = false;
50 let mut buffer_view = BufferView::from_buffer(gl, buffer);
51 buffer_view.interactive = false;
52 let buffer_view = Arc::new(Mutex::new(buffer_view));
53 let size = font.size;
54 let last_updated_font = font.clone();
55 Self {
56 id,
57 buffer_view,
58 original_font: font.clone(),
59 last_updated_font,
60 font,
61 width: size.width,
62 height: size.height,
63 selected_char_opt: Some('A'),
64 undo_stack: Arc::new(Mutex::new(Vec::new())),
65 redo_stack: Vec::new(),
66 old_data: None,
67 send_update_message: false,
68 }
69 }
70
71 pub fn draw_glyph(ui: &mut egui::Ui, font: &BitFont, style: DrawGlyphStyle, ch: char) -> egui::Response {
72 let scale = 3.;
73 let (id, stroke_rect) = ui.allocate_space(Vec2::new(scale * font.size.width as f32, scale * font.size.height as f32));
74 let response = ui.interact(stroke_rect, id, Sense::click());
75 let col = if response.hovered() {
76 match style {
77 DrawGlyphStyle::Normal => Color32::LIGHT_GRAY,
78 DrawGlyphStyle::Selected => Color32::WHITE,
79 DrawGlyphStyle::GrayOut => Color32::GRAY,
80 }
81 } else {
82 match style {
83 DrawGlyphStyle::Normal => Color32::GRAY,
84 DrawGlyphStyle::Selected => Color32::YELLOW,
85 DrawGlyphStyle::GrayOut => Color32::DARK_GRAY,
86 }
87 };
88
89 let painter = ui.painter_at(stroke_rect);
90 painter.rect_filled(stroke_rect, Rounding::ZERO, Color32::BLACK);
91 let s = font.size;
92 if let Some(glyph) = font.get_glyph(ch) {
93 for y in 0..s.height {
94 for x in 0..s.width {
95 if glyph.data[y as usize] & (128 >> x) != 0 {
96 painter.rect_filled(
97 Rect::from_min_size(
98 Pos2::new(stroke_rect.left() + x as f32 * scale, stroke_rect.top() + y as f32 * scale),
99 Vec2::new(scale, scale),
100 ),
101 Rounding::ZERO,
102 col,
103 );
104 }
105 }
106 }
107 }
108 response
109 }
110
111 pub fn update_tile_area(&mut self) {
112 let lock = &mut self.buffer_view.lock();
113 let buf = lock.get_buffer_mut();
114 buf.set_font(0, self.font.clone());
115
116 let ch = self.selected_char_opt.unwrap_or(' ');
117 for y in 0..buf.get_width() {
118 for x in 0..buf.get_height() {
119 buf.layers[0].set_char(
120 (x, y),
121 icy_engine::AttributedChar {
122 ch,
123 attribute: TextAttribute::default(),
124 },
125 );
126 }
127 }
128 self.send_update_message = true;
129 lock.redraw_view();
130 }
131
132 pub fn edit_glyph(&mut self) -> impl egui::Widget + '_ {
133 move |ui: &mut egui::Ui| {
134 let scale = 20.;
135 let border = 2.;
136
137 let left_ruler = 20.0;
138 let top_ruler = 20.0;
139
140 let (id, stroke_rect) = ui.allocate_space(Vec2::new(
141 1. + (border + scale) * self.font.size.width as f32 + left_ruler,
142 1. + (border + scale) * self.font.size.height as f32 + top_ruler,
143 ));
144 let mut response = ui.interact(stroke_rect, id, Sense::click_and_drag());
145
146 let painter = ui.painter_at(stroke_rect);
147 painter.rect_filled(stroke_rect, Rounding::ZERO, Color32::DARK_GRAY);
148
149 let s = self.font.size;
150
151 /* if response.clicked() {
152 if let Some(pos) = response.hover_pos() {
153 if let Some(number) = self.selected_char_opt {
154 if let Some(glyph) = self.font.get_glyph_mut(number) {
155 println!("click!");
156 let y = ((pos.y - stroke_rect.top()) / (scale + border)) as usize;
157 let x = ((pos.x - stroke_rect.left()) / (scale + border)) as usize;
158 if glyph.data[y] & (128 >> x) != 0 {
159 println!("unset!");
160 glyph.data[y] &= !(128 >> x);
161 } else {
162 println!("set!");
163 glyph.data[y] |= 128 >> x;
164 }
165 self.is_dirty = true;
166 response.mark_changed();
167 }
168 }
169 }
170 } else { */
171 if response.drag_started_by(egui::PointerButton::Primary) || response.drag_started_by(egui::PointerButton::Secondary) {
172 self.start_edit();
173 }
174
175 if response.drag_released_by(egui::PointerButton::Primary) || response.drag_released_by(egui::PointerButton::Secondary) {
176 self.end_edit();
177 }
178
179 if response.dragged_by(egui::PointerButton::Primary) {
180 if let Some(pos) = response.hover_pos() {
181 if let Some(number) = self.selected_char_opt {
182 if let Some(glyph) = self.font.get_glyph_mut(number) {
183 let y = ((pos.y - left_ruler - stroke_rect.top()) / (scale + border)) as usize;
184 let x = ((pos.x - top_ruler - stroke_rect.left()) / (scale + border)) as usize;
185 if y < glyph.data.len() && x < 8 {
186 glyph.data[y] |= 128 >> x;
187 self.update_tile_area();
188 response.mark_changed();
189 }
190 }
191 }
192 }
193 }
194
195 if response.dragged_by(egui::PointerButton::Secondary) {
196 if let Some(pos) = response.hover_pos() {
197 if let Some(number) = self.selected_char_opt {
198 if let Some(glyph) = self.font.get_glyph_mut(number) {
199 let y = ((pos.y - left_ruler - stroke_rect.top()) / (scale + border)) as usize;
200 let x = ((pos.x - top_ruler - stroke_rect.left()) / (scale + border)) as usize;
201 if y < glyph.data.len() && x < 8 {
202 glyph.data[y] &= !(128 >> x);
203 self.update_tile_area();
204 response.mark_changed();
205 }
206 }
207 }
208 }
209 }
210 if let Some(number) = self.selected_char_opt {
211 if let Some(glyph) = self.font.get_glyph_mut(number) {
212 painter.rect_filled(
213 Rect::from_min_size(
214 Pos2::new(stroke_rect.left(), stroke_rect.top()),
215 Vec2::new(
216 2. + left_ruler + s.width as f32 * (border + scale),
217 2. + top_ruler + s.height as f32 * (border + scale),
218 ),
219 ),
220 Rounding::ZERO,
221 ui.style().visuals.extreme_bg_color,
222 );
223
224 for x in 0..s.width {
225 let pos = Pos2::new(
226 2. + left_ruler + stroke_rect.left() + (x as f32 + 0.5) * (border + scale),
227 2. + top_ruler / 2. + stroke_rect.top(),
228 );
229 let col = if let Some(pos) = response.hover_pos() {
230 if x == ((pos.x - (2. + left_ruler + stroke_rect.left())) / (border + scale)) as i32 {
231 ui.style().visuals.strong_text_color()
232 } else {
233 ui.style().visuals.text_color()
234 }
235 } else {
236 ui.style().visuals.text_color()
237 };
238
239 painter.text(
240 pos,
241 Align2::CENTER_CENTER,
242 (x + 1).to_string(),
243 FontId::new(12.0, FontFamily::Proportional),
244 col,
245 );
246 }
247 for y in 0..s.height {
248 let pos = Pos2::new(
249 2. + left_ruler / 2. + stroke_rect.left(),
250 2. + top_ruler + stroke_rect.top() + (y as f32 + 0.5) * (border + scale),
251 );
252 let col = if let Some(pos) = response.hover_pos() {
253 if y == ((pos.y - (2. + top_ruler + stroke_rect.top())) / (border + scale)) as i32 {
254 ui.style().visuals.strong_text_color()
255 } else {
256 ui.style().visuals.text_color()
257 }
258 } else {
259 ui.style().visuals.text_color()
260 };
261
262 painter.text(
263 pos,
264 Align2::CENTER_CENTER,
265 (y + 1).to_string(),
266 FontId::new(12.0, FontFamily::Proportional),
267 col,
268 );
269 }
270
271 for y in 0..s.height {
272 for x in 0..s.width {
273 let rect = Rect::from_min_size(
274 Pos2::new(
275 2. + left_ruler + stroke_rect.left() + x as f32 * (border + scale),
276 2. + top_ruler + stroke_rect.top() + y as f32 * (border + scale),
277 ),
278 Vec2::new(scale, scale),
279 );
280 let col = if glyph.data[y as usize] & (128 >> x) != 0 {
281 if let Some(pos) = response.hover_pos() {
282 if rect.contains(pos) {
283 Color32::WHITE
284 } else {
285 Color32::GRAY
286 }
287 } else {
288 Color32::GRAY
289 }
290 } else if let Some(pos) = response.hover_pos() {
291 if rect.contains(pos) {
292 Color32::DARK_GRAY
293 } else {
294 Color32::BLACK
295 }
296 } else {
297 Color32::BLACK
298 };
299 painter.rect_filled(rect, Rounding::ZERO, col);
300 }
301 }
302 }
303 }
304 response
305 }
306 }
307
308 fn push_undo(&mut self, mut op: Box<dyn UndoOperation>) -> EngineResult<()> {
309 op.redo(self)?;
310 self.undo_stack.lock().push(op);
311 self.redo_stack.clear();
312 self.update_tile_area();
313 Ok(())
314 }
315
316 fn clear_selected_glyph(&mut self) -> EngineResult<()> {
317 if let Some(number) = self.selected_char_opt {
318 let op = undo::ClearGlyph::new(number);
319 self.push_undo(Box::new(op))?;
320 }
321 Ok(())
322 }
323
324 fn inverse_selected_glyph(&mut self) -> EngineResult<()> {
325 if let Some(number) = self.selected_char_opt {
326 let op = undo::InverseGlyph::new(number);
327 self.push_undo(Box::new(op))?;
328 }
329 Ok(())
330 }
331
332 fn left_selected_glyph(&mut self) -> EngineResult<()> {
333 if let Some(number) = self.selected_char_opt {
334 let op = undo::LeftGlyph::new(number);
335 self.push_undo(Box::new(op))?;
336 }
337 Ok(())
338 }
339
340 fn right_selected_glyph(&mut self) -> EngineResult<()> {
341 if let Some(number) = self.selected_char_opt {
342 let op = undo::RightGlyph::new(number);
343 self.push_undo(Box::new(op))?;
344 }
345 Ok(())
346 }
347
348 fn up_selected_glyph(&mut self) -> EngineResult<()> {
349 if let Some(number) = self.selected_char_opt {
350 let op = undo::UpGlyph::new(number);
351 self.push_undo(Box::new(op))?;
352 }
353 Ok(())
354 }
355
356 fn down_selected_glyph(&mut self) -> EngineResult<()> {
357 if let Some(number) = self.selected_char_opt {
358 let op = undo::DownGlyph::new(number);
359 self.push_undo(Box::new(op))?;
360 }
361 Ok(())
362 }
363
364 fn flip_x_selected_glyph(&mut self) -> EngineResult<()> {
365 if let Some(number) = self.selected_char_opt {
366 let op = undo::FlipX::new(number);
367 self.push_undo(Box::new(op))?;
368 }
369 Ok(())
370 }
371
372 fn flip_y_selected_glyph(&mut self) -> EngineResult<()> {
373 if let Some(number) = self.selected_char_opt {
374 let op = undo::FlipY::new(number);
375 self.push_undo(Box::new(op))?;
376 }
377 Ok(())
378 }
379
380 fn resize_font(&mut self) -> EngineResult<()> {
381 let old_font = self.font.clone();
382 let mut new_font = self.font.clone();
383
384 for glyph in new_font.glyphs.values_mut() {
385 glyph.data.resize(self.height as usize, 0);
386 }
387 new_font.size = Size::new(self.width, self.height);
388
389 let op = undo::ResizeFont::new(old_font, new_font);
390 self.push_undo(Box::new(op))?;
391 Ok(())
392 }
393
394 fn start_edit(&mut self) {
395 if let Some(number) = self.selected_char_opt {
396 if let Some(glyph) = self.font.get_glyph_mut(number) {
397 self.old_data = Some(glyph.data.clone());
398 }
399 }
400 }
401
402 fn end_edit(&mut self) {
403 if self.old_data.is_none() {
404 return;
405 }
406 if let Some(number) = self.selected_char_opt {
407 if let Some(glyph) = self.font.get_glyph(number) {
408 if let Some(old_data) = self.old_data.take() {
409 let op = undo::Edit::new(number, glyph.data.clone(), old_data);
410 self.undo_stack.lock().push(Box::new(op));
411 self.redo_stack.clear();
412 } else {
413 log::error!("no old_data for BitFontEditor:end_edit");
414 }
415 }
416 }
417 }
418 }
419
420 impl ClipboardHandler for BitFontEditor {
421 fn can_copy(&self) -> bool {
422 self.selected_char_opt.is_some()
423 }
424
425 fn copy(&mut self) -> EngineResult<()> {
426 if let Some(ch) = self.selected_char_opt {
427 if let Some(data) = self.font.get_clipboard_data(ch) {
428 push_data(BITFONT_GLYPH, &data)?;
429 }
430 }
431 Ok(())
432 }
433
434 fn can_paste(&self) -> bool {
435 if self.selected_char_opt.is_none() {
436 return false;
437 }
438
439 pop_data(BITFONT_GLYPH).is_some()
440 }
441
442 fn paste(&mut self) -> EngineResult<()> {
443 if let Some(data) = pop_data(BITFONT_GLYPH) {
444 let (_, g) = Glyph::from_clipbard_data(&data);
445 if let Some(ch) = self.selected_char_opt {
446 let op = undo::Paste::new(ch, g);
447 self.push_undo(Box::new(op))?;
448 }
449 }
450 Ok(())
451 }
452 }
453
454 impl UndoHandler for BitFontEditor {
455 fn undo_description(&self) -> Option<String> {
456 self.undo_stack.lock().last().map(|op| op.get_description())
457 }
458
459 fn can_undo(&self) -> bool {
460 !self.undo_stack.lock().is_empty()
461 }
462
463 fn undo(&mut self) -> EngineResult<Option<Message>> {
464 let Some(mut op) = self.undo_stack.lock().pop() else {
465 return Ok(None);
466 };
467
468 op.undo(self)?;
469 self.redo_stack.push(op);
470 self.update_tile_area();
471 self.send_update_message = true;
472 Ok(None)
473 }
474
475 fn redo_description(&self) -> Option<String> {
476 self.redo_stack.last().map(|op| op.get_description())
477 }
478
479 fn can_redo(&self) -> bool {
480 !self.redo_stack.is_empty()
481 }
482
483 fn redo(&mut self) -> EngineResult<Option<Message>> {
484 if let Some(mut op) = self.redo_stack.pop() {
485 op.redo(self)?;
486 self.undo_stack.lock().push(op);
487 return Ok(None);
488 }
489 self.update_tile_area();
490 self.send_update_message = true;
491 Ok(None)
492 }
493 }
494
495 impl Document for BitFontEditor {
496 fn default_extension(&self) -> &'static str {
497 "psf"
498 }
499
500 fn undo_stack_len(&self) -> usize {
501 self.undo_stack.lock().len()
502 }
503
504 fn show_ui(&mut self, ui: &mut eframe::egui::Ui, _cur_tool: &mut Box<dyn Tool>, _selected_tool: usize, _options: &DocumentOptions) -> Option<Message> {
505 let mut message = None;
506 ui.add_space(16.);
507 ui.vertical_centered(|ui| {
508 ui.horizontal(|ui| {
509 ui.add_space(120.);
510 ui.add(self.edit_glyph());
511
512 ui.vertical(|ui| {
513 ui.add_space(20.);
514 ui.horizontal(|ui| {
515 if ui.button(fl!(crate::LANGUAGE_LOADER, "font-editor-clear")).clicked() {
516 message = to_message(self.clear_selected_glyph());
517 }
518 if ui.button(fl!(crate::LANGUAGE_LOADER, "font-editor-inverse")).clicked() {
519 message = to_message(self.inverse_selected_glyph());
520 }
521 });
522 ui.add_space(8.);
523 ui.horizontal(|ui| {
524 ui.add_space(14.);
525
526 if ui.button("⬆").clicked() {
527 message = to_message(self.up_selected_glyph());
528 }
529 });
530
531 ui.horizontal(|ui| {
532 if ui.button("⬅").clicked() {
533 message = to_message(self.left_selected_glyph());
534 }
535
536 if ui.button("➡").clicked() {
537 message = to_message(self.right_selected_glyph());
538 }
539 });
540
541 ui.horizontal(|ui| {
542 ui.add_space(14.);
543
544 if ui.button("⬇").clicked() {
545 message = to_message(self.down_selected_glyph());
546 }
547 });
548 ui.add_space(8.);
549
550 ui.horizontal(|ui| {
551 if ui.button(fl!(crate::LANGUAGE_LOADER, "font-editor-flip_x")).clicked() {
552 message = to_message(self.flip_x_selected_glyph());
553 }
554
555 if ui.button(fl!(crate::LANGUAGE_LOADER, "font-editor-flip_y")).clicked() {
556 message = to_message(self.flip_y_selected_glyph());
557 }
558 });
559
560 egui::Grid::new("some_unique_id").num_columns(2).spacing([4.0, 8.0]).show(ui, |ui| {
561 ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| {
562 ui.label(fl!(crate::LANGUAGE_LOADER, "new-file-width"));
563 });
564 ui.add(egui::Slider::new(&mut self.width, 2..=8));
565 ui.end_row();
566
567 ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| {
568 ui.label(fl!(crate::LANGUAGE_LOADER, "new-file-height"));
569 });
570 ui.add(egui::Slider::new(&mut self.height, 2..=19));
571 ui.end_row();
572 });
573
574 if (self.width != self.font.size.width || self.height != self.font.size.height) && ui.button("Resize").clicked() {
575 message = to_message(self.resize_font());
576 }
577 });
578
579 ui.vertical(|ui| {
580 ui.heading(fl!(crate::LANGUAGE_LOADER, "font-editor-tile_area"));
581 let mut scale = unsafe { SETTINGS.get_scale() };
582 if self.buffer_view.lock().get_buffer().use_aspect_ratio() {
583 scale.y *= 1.35;
584 }
585 let opt = icy_engine_egui::TerminalOptions {
586 stick_to_bottom: false,
587 scale: Some(Vec2::new(2.0, 2.0)),
588 monitor_settings: unsafe { SETTINGS.monitor_settings.clone() },
589 marker_settings: unsafe { SETTINGS.marker_settings.clone() },
590 id: Some(Id::new(self.id + 20000)),
591 ..Default::default()
592 };
593 self.buffer_view.lock().get_caret_mut().set_is_visible(false);
594 let (_, _) = show_terminal_area(ui, self.buffer_view.clone(), opt);
595 });
596 });
597 });
598
599 ui.label(fl!(crate::LANGUAGE_LOADER, "font-editor-table", length = (self.font.length - 1).to_string()));
600 egui::ScrollArea::vertical().show(ui, |ui| {
601 ui.horizontal_wrapped(|ui| {
602 for i in 0..self.font.length {
603 let ch = unsafe { char::from_u32_unchecked(i as u32) };
604 let mut style = DrawGlyphStyle::Normal;
605 if let Some(ch2) = self.selected_char_opt {
606 if ch == ch2 {
607 style = DrawGlyphStyle::Selected
608 }
609 }
610 let response = BitFontEditor::draw_glyph(ui, &self.font, style, ch);
611 if response.clicked() {
612 self.selected_char_opt = Some(ch);
613 self.update_tile_area();
614 }
615
616 response.on_hover_ui(|ui| {
617 ui.horizontal(|ui| {
618 ui.label(RichText::new(fl!(crate::LANGUAGE_LOADER, "font-view-char_label")).small());
619 ui.label(RichText::new(format!("{0}/0x{0:02X}", i)).small().color(Color32::WHITE));
620 });
621 ui.horizontal(|ui| {
622 ui.label(RichText::new(fl!(crate::LANGUAGE_LOADER, "font-view-ascii_label")).small());
623 ui.label(
624 RichText::new(format!("'{0}'", unsafe { char::from_u32_unchecked(i as u32) }))
625 .small()
626 .color(Color32::WHITE),
627 );
628 });
629 });
630 }
631 })
632 });
633
634 if message.is_none() && self.send_update_message {
635 message = Some(Message::UpdateFont(Box::new((self.last_updated_font.clone(), self.font.clone()))));
636 self.last_updated_font = self.font.clone();
637 self.send_update_message = false;
638 }
639
640 message
641 }
642
643 fn get_bytes(&mut self, _path: &Path) -> TerminalResult<Vec<u8>> {
644 self.font.to_psf2_bytes()
645 }
646
647 fn get_ansi_editor_mut(&mut self) -> Option<&mut AnsiEditor> {
648 None
649 }
650
651 fn get_ansi_editor(&self) -> Option<&AnsiEditor> {
652 None
653 }
654
655 fn inform_save(&mut self) {
656 self.original_font = self.font.clone();
657 }
658
659 fn destroy(&self, gl: &glow::Context) -> Option<Message> {
660 self.buffer_view.lock().destroy(gl);
661 Some(Message::UpdateFont(Box::new((self.last_updated_font.clone(), self.original_font.clone()))))
662 }
663 }