font_imp.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
---
font_imp.rs (17614B)
---
1 use std::{fs, io::Read, path::Path, sync::Arc, thread};
2
3 use crate::{AnsiEditor, Message, Settings};
4
5 use super::{Event, MKey, MModifiers, Position, Tool};
6 use eframe::{
7 egui::{self, Button, RichText},
8 epaint::{FontFamily, FontId},
9 };
10 use egui::mutex::Mutex;
11 use i18n_embed_fl::fl;
12 use icy_engine::{editor::OperationType, Size, TextPane, TheDrawFont};
13 use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
14 use walkdir::{DirEntry, WalkDir};
15 pub struct FontTool {
16 pub selected_font: Arc<Mutex<i32>>,
17 pub fonts: Arc<Mutex<Vec<TheDrawFont>>>,
18 pub sizes: Vec<Size>,
19 }
20
21 impl FontTool {
22 /*pub fn get_selected_font(&self) -> Option<&TheDrawFont> {
23 self.fonts.get(self.selected_font as usize)
24 }*/
25
26 pub(crate) fn is_hidden(entry: &DirEntry) -> bool {
27 entry.file_name().to_str().map_or(false, |s| s.starts_with('.'))
28 }
29
30 pub fn install_watcher(&self) {
31 if let Ok(tdf_dir) = Settings::get_tdf_diretory() {
32 let fonts = self.fonts.clone();
33 thread::spawn(move || loop {
34 if watch(tdf_dir.as_path(), &fonts).is_err() {
35 return;
36 }
37 });
38 }
39 }
40
41 pub fn load_fonts(&mut self) {
42 if let Ok(tdf_dir) = Settings::get_tdf_diretory() {
43 self.fonts = Arc::new(Mutex::new(load_fonts(tdf_dir.as_path())));
44 }
45 }
46 }
47
48 fn load_fonts(tdf_dir: &Path) -> Vec<TheDrawFont> {
49 let mut fonts = Vec::new();
50 let walker = WalkDir::new(tdf_dir).into_iter();
51 for entry in walker.filter_entry(|e| !FontTool::is_hidden(e)) {
52 if let Err(e) = entry {
53 log::error!("Can't load tdf font library: {e}");
54 break;
55 }
56 let Ok(entry) = entry else {
57 continue;
58 };
59 let path = entry.path();
60
61 if path.is_dir() {
62 continue;
63 }
64 let extension = path.extension();
65 if extension.is_none() {
66 continue;
67 }
68 let Some(extension) = extension else {
69 continue;
70 };
71 let extension = extension.to_str();
72 let Some(extension) = extension else {
73 continue;
74 };
75
76 let extension = extension.to_lowercase();
77
78 if extension == "tdf" {
79 if let Ok(loaded_fonts) = TheDrawFont::load(path) {
80 fonts.extend(loaded_fonts);
81 }
82 }
83
84 if extension == "zip" {
85 match fs::File::open(path) {
86 Ok(mut file) => {
87 let mut data = Vec::new();
88 file.read_to_end(&mut data).unwrap_or_default();
89 read_zip_archive(data, &mut fonts);
90 }
91
92 Err(err) => {
93 log::error!("Failed to open zip file: {}", err);
94 }
95 }
96 }
97 }
98 fonts
99 }
100
101 fn read_zip_archive(data: Vec<u8>, fonts: &mut Vec<TheDrawFont>) {
102 let file = std::io::Cursor::new(data);
103 match zip::ZipArchive::new(file) {
104 Ok(mut archive) => {
105 for i in 0..archive.len() {
106 match archive.by_index(i) {
107 Ok(mut file) => {
108 if let Some(name) = file.enclosed_name() {
109 if name.to_string_lossy().to_ascii_lowercase().ends_with(".tdf") {
110 let mut data = Vec::new();
111 file.read_to_end(&mut data).unwrap_or_default();
112
113 if let Ok(loaded_fonts) = TheDrawFont::from_tdf_bytes(&data) {
114 fonts.extend(loaded_fonts);
115 }
116 } else if name.to_string_lossy().to_ascii_lowercase().ends_with(".zip") {
117 let mut data = Vec::new();
118 file.read_to_end(&mut data).unwrap_or_default();
119 read_zip_archive(data, fonts);
120 }
121 }
122 }
123 Err(err) => {
124 log::error!("Error reading zip file: {}", err);
125 }
126 }
127 }
128 }
129 Err(err) => {
130 log::error!("Error reading zip archive: {}", err);
131 }
132 }
133 }
134
135 impl Tool for FontTool {
136 fn get_icon(&self) -> &egui::Image<'static> {
137 &super::icons::FONT_SVG
138 }
139
140 fn tool_name(&self) -> String {
141 fl!(crate::LANGUAGE_LOADER, "tool-tdf_name")
142 }
143
144 fn tooltip(&self) -> String {
145 fl!(crate::LANGUAGE_LOADER, "tool-tdf_tooltip")
146 }
147
148 fn use_selection(&self) -> bool {
149 false
150 }
151
152 fn show_ui(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui, _editor_opt: Option<&mut AnsiEditor>) -> Option<Message> {
153 let mut select = false;
154 let font_count = self.fonts.lock().len();
155 let selected_font = *self.selected_font.lock();
156
157 ui.vertical_centered(|ui| {
158 ui.label(fl!(crate::LANGUAGE_LOADER, "font_tool_current_font_label"));
159
160 let mut selected_text = fl!(crate::LANGUAGE_LOADER, "font_tool_no_font");
161
162 if selected_font >= 0 && (selected_font as usize) < font_count {
163 if let Some(font) = self.fonts.lock().get(selected_font as usize) {
164 selected_text = font.name.clone();
165 }
166 }
167 let selected_text = RichText::new(selected_text).font(FontId::new(18.0, FontFamily::Proportional));
168 select = ui.add_enabled(font_count > 0, Button::new(selected_text)).clicked();
169 });
170
171 if font_count == 0 {
172 ui.add_space(32.0);
173 let mut msg = None;
174 ui.vertical_centered(|ui| {
175 ui.label(fl!(crate::LANGUAGE_LOADER, "font_tool_no_fonts_label"));
176 if ui.button(fl!(crate::LANGUAGE_LOADER, "font_tool_open_directory_button")).clicked() {
177 msg = Some(Message::OpenTdfDirectory);
178 }
179 });
180 if msg.is_some() {
181 return msg;
182 }
183 }
184
185 if selected_font >= 0 && (selected_font as usize) < font_count {
186 ui.add_space(8.0);
187 let left_border = 16.0;
188 ui.vertical_centered(|ui| {
189 ui.horizontal(|ui| {
190 ui.add_space(left_border);
191
192 if let Some(font) = self.fonts.lock().get(selected_font as usize) {
193 for ch in '!'..'9' {
194 ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
195 let color = if font.has_char(ch as u8) {
196 ui.style().visuals.strong_text_color()
197 } else {
198 ui.style().visuals.text_color()
199 };
200
201 ui.colored_label(color, RichText::new(ch.to_string()).font(FontId::new(14.0, FontFamily::Monospace)));
202 }
203 }
204 });
205
206 ui.horizontal(|ui| {
207 ui.add_space(left_border);
208
209 if let Some(font) = self.fonts.lock().get(selected_font as usize) {
210 for ch in '9'..'Q' {
211 ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
212 let color = if font.has_char(ch as u8) {
213 ui.style().visuals.strong_text_color()
214 } else {
215 ui.style().visuals.text_color()
216 };
217
218 ui.colored_label(color, RichText::new(ch.to_string()).font(FontId::new(14.0, FontFamily::Monospace)));
219 }
220 }
221 });
222
223 ui.horizontal(|ui| {
224 ui.add_space(left_border);
225 if let Some(font) = self.fonts.lock().get(selected_font as usize) {
226 ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
227 for ch in 'Q'..'i' {
228 let color = if font.has_char(ch as u8) {
229 ui.style().visuals.strong_text_color()
230 } else {
231 ui.style().visuals.text_color()
232 };
233
234 ui.colored_label(color, RichText::new(ch.to_string()).font(FontId::new(14.0, FontFamily::Monospace)));
235 }
236 }
237 });
238 ui.horizontal(|ui| {
239 ui.add_space(left_border);
240 if let Some(font) = self.fonts.lock().get(selected_font as usize) {
241 ui.spacing_mut().item_spacing = eframe::epaint::Vec2::new(0.0, 0.0);
242 for ch in 'i'..='~' {
243 let color = if font.has_char(ch as u8) {
244 ui.style().visuals.strong_text_color()
245 } else {
246 ui.style().visuals.text_color()
247 };
248
249 ui.colored_label(color, RichText::new(ch.to_string()).font(FontId::new(14.0, FontFamily::Monospace)));
250 }
251 }
252 });
253 });
254 }
255
256 if font_count > 0 {
257 if let Some(font) = self.fonts.lock().get(selected_font as usize) {
258 if matches!(font.font_type, icy_engine::FontType::Outline) {
259 ui.add_space(32.0);
260 let mut msg = None;
261 ui.vertical_centered(|ui| {
262 if ui.button(fl!(crate::LANGUAGE_LOADER, "font_tool_select_outline_button")).clicked() {
263 msg = Some(Message::ShowOutlineDialog);
264 }
265 });
266 if msg.is_some() {
267 return msg;
268 }
269 }
270 }
271 }
272
273 if select {
274 Some(Message::SelectFontDialog(self.fonts.clone(), self.selected_font.clone()))
275 } else {
276 None
277 }
278 }
279
280 fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position, _pos_abs: Position, _response: &egui::Response) -> Option<Message> {
281 if button == 1 {
282 editor.set_caret_position(pos);
283 editor.buffer_view.lock().clear_selection();
284 }
285 None
286 }
287
288 fn handle_hover(&mut self, _ui: &egui::Ui, response: egui::Response, _editor: &mut AnsiEditor, _cur: Position, _cur_abs: Position) -> egui::Response {
289 response.on_hover_cursor(egui::CursorIcon::Text)
290 }
291
292 fn handle_key(&mut self, editor: &mut AnsiEditor, key: MKey, modifier: MModifiers) -> Event {
293 let selected_font = *self.selected_font.lock();
294
295 if selected_font < 0 || selected_font >= self.fonts.lock().len() as i32 {
296 return Event::None;
297 }
298 let font = &self.fonts.lock()[selected_font as usize];
299 let pos = editor.buffer_view.lock().get_caret().get_position();
300
301 match key {
302 MKey::Down => {
303 editor.set_caret(pos.x, pos.y + 1);
304 }
305 MKey::Up => {
306 editor.set_caret(pos.x, pos.y - 1);
307 }
308 MKey::Left => {
309 editor.set_caret(pos.x - 1, pos.y);
310 }
311 MKey::Right => {
312 editor.set_caret(pos.x + 1, pos.y);
313 }
314
315 MKey::Home => {
316 if let MModifiers::Control = modifier {
317 let end = editor.buffer_view.lock().get_buffer().get_width();
318 for i in 0..end {
319 if !editor.get_char_from_cur_layer(pos.with_x(i)).is_transparent() {
320 editor.set_caret(i, pos.y);
321 return Event::None;
322 }
323 }
324 }
325 editor.set_caret(0, pos.y);
326 }
327
328 MKey::End => {
329 if let MModifiers::Control = modifier {
330 let end = editor.buffer_view.lock().get_buffer().get_width();
331 for i in (0..end).rev() {
332 if !editor.get_char_from_cur_layer(pos.with_x(i)).is_transparent() {
333 editor.set_caret(i, pos.y);
334 return Event::None;
335 }
336 }
337 }
338 let w = editor.buffer_view.lock().get_buffer().get_width();
339 editor.set_caret(w - 1, pos.y);
340 }
341
342 MKey::Return => {
343 editor.set_caret(0, pos.y + font.get_font_height());
344 /*
345 if let Some(size) = self.sizes.last() {
346 editor.set_caret(0,pos.y + size.height as i32);
347 } else {
348 editor.set_caret(0,pos.y + 1);
349 }*/
350 self.sizes.clear();
351 }
352
353 MKey::Backspace => {
354 let mut use_backspace = true;
355 {
356 let mut render = false;
357 let mut reverse_count = 0;
358
359 let op = if let Ok(stack) = editor.buffer_view.lock().get_edit_state().get_undo_stack().lock() {
360 for i in (0..stack.len()).rev() {
361 match stack[i].get_operation_type() {
362 OperationType::RenderCharacter => {
363 if reverse_count == 0 {
364 render = true;
365 reverse_count = i;
366 break;
367 }
368 reverse_count -= 1;
369 }
370 OperationType::ReversedRenderCharacter => {
371 reverse_count += 1;
372 }
373 OperationType::Unknown => {
374 render = false;
375 }
376 }
377 }
378 if reverse_count < stack.len() {
379 stack[reverse_count].try_clone()
380 } else {
381 None
382 }
383 } else {
384 None
385 };
386
387 if render {
388 if let Some(op) = op {
389 let _ = editor.buffer_view.lock().get_edit_state_mut().push_reverse_undo(
390 fl!(crate::LANGUAGE_LOADER, "undo-delete_character"),
391 op,
392 OperationType::ReversedRenderCharacter,
393 );
394 use_backspace = false;
395 }
396 }
397 }
398
399 if use_backspace {
400 editor.backspace();
401 }
402 }
403 MKey::Character(ch) => {
404 let c_pos = editor.get_caret_position();
405 let _undo = editor
406 .buffer_view
407 .lock()
408 .get_edit_state_mut()
409 .begin_typed_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-render_character"), OperationType::RenderCharacter);
410
411 let outline_style = if editor.outline_font_mode {
412 usize::MAX
413 } else {
414 Settings::get_font_outline_style()
415 };
416 editor.buffer_view.lock().get_edit_state_mut().set_outline_style(outline_style);
417
418 let _ = editor.buffer_view.lock().get_edit_state_mut().undo_caret_position();
419
420 let opt_size: Option<Size> = font.render(editor.buffer_view.lock().get_edit_state_mut(), ch as u8);
421 if let Some(size) = opt_size {
422 editor.set_caret(c_pos.x + size.width + font.spaces, c_pos.y);
423 let new_pos = editor.get_caret_position();
424 self.sizes.push(Size {
425 width: (new_pos.x - c_pos.x),
426 height: size.height,
427 });
428 } else {
429 editor.type_key(unsafe { char::from_u32_unchecked(ch as u32) });
430 self.sizes.push(Size::new(1, 1));
431 }
432 }
433 _ => {}
434 }
435 Event::None
436 }
437 }
438
439 fn watch(path: &Path, fonts: &Arc<Mutex<Vec<TheDrawFont>>>) -> notify::Result<()> {
440 let (tx, rx) = std::sync::mpsc::channel();
441
442 // Automatically select the best implementation for your platform.
443 // You can also access each implementation directly e.g. INotifyWatcher.
444 let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
445
446 // Add a path to be watched. All files and directories at that path and
447 // below will be monitored for changes.
448 watcher.watch(path.as_ref(), RecursiveMode::Recursive)?;
449
450 for res in rx {
451 match res {
452 Ok(_) => {
453 fonts.lock().clear();
454 fonts.lock().extend(load_fonts(path));
455
456 break;
457 }
458 Err(e) => log::error!("watch error: {e:}"),
459 }
460 }
461
462 Ok(())
463 }