select_tdf_font_dialog.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
       ---
       select_tdf_font_dialog.rs (13216B)
       ---
            1 use std::{fs, sync::Arc};
            2 
            3 use eframe::{
            4     egui::{self, Response, Sense, TextEdit, TextStyle, WidgetText},
            5     epaint::{ahash::HashMap, ColorImage, FontFamily, FontId, Pos2, Rect, Rounding, Stroke, Vec2},
            6 };
            7 use egui::{load::SizedTexture, mutex::Mutex, Image, TextureHandle, TextureOptions};
            8 use egui_file::FileDialog;
            9 use egui_modal::Modal;
           10 use i18n_embed_fl::fl;
           11 use icy_engine::{editor::EditState, Buffer, Rectangle, Size, TextPane, TheDrawFont};
           12 
           13 use crate::{MainWindow, Message};
           14 
           15 pub struct SelectFontDialog {
           16     fonts: Arc<Mutex<Vec<TheDrawFont>>>,
           17     selected_font_arc: Arc<Mutex<i32>>,
           18     selected_font: i32,
           19     pub do_select: bool,
           20     filter: String,
           21     show_outline: bool,
           22     show_color: bool,
           23     show_block: bool,
           24 
           25     export_data: Option<Vec<u8>>,
           26     export_dialog: Option<FileDialog>,
           27 
           28     image_cache: HashMap<usize, TextureHandle>,
           29 }
           30 
           31 impl SelectFontDialog {
           32     pub fn new(fonts: Arc<Mutex<Vec<TheDrawFont>>>, selected_font_arc: Arc<Mutex<i32>>) -> Self {
           33         let selected_font = *selected_font_arc.lock();
           34 
           35         Self {
           36             do_select: false,
           37             fonts,
           38             selected_font_arc,
           39             selected_font,
           40             filter: String::new(),
           41             show_outline: true,
           42             show_color: true,
           43             show_block: true,
           44             image_cache: HashMap::default(),
           45             export_dialog: None,
           46             export_data: None,
           47         }
           48     }
           49 
           50     pub fn draw_font_row(&mut self, ui: &mut egui::Ui, cur_font: usize, row_height: f32, is_selected: bool) -> Response {
           51         let font = &self.fonts.lock()[cur_font];
           52         let (id, rect) = ui.allocate_space([ui.available_width(), row_height].into());
           53         let response = ui.interact(rect, id, Sense::click());
           54         if response.hovered() {
           55             ui.painter()
           56                 .rect_filled(rect.expand(1.0), Rounding::same(4.0), ui.style().visuals.widgets.active.bg_fill);
           57         } else if is_selected {
           58             ui.painter()
           59                 .rect_filled(rect.expand(1.0), Rounding::same(4.0), ui.style().visuals.extreme_bg_color);
           60         }
           61 
           62         let text_color = if is_selected {
           63             ui.style().visuals.strong_text_color()
           64         } else {
           65             ui.style().visuals.text_color()
           66         };
           67 
           68         let font_id = TextStyle::Button.resolve(ui.style());
           69         let text: WidgetText = font.name.clone().into();
           70         let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);
           71         ui.painter().galley_with_override_text_color(
           72             egui::Align2::LEFT_TOP.align_size_within_rect(galley.size(), rect.shrink(4.0)).min,
           73             galley,
           74             text_color,
           75         );
           76 
           77         let mut x = 0.;
           78         let mut y = 26.;
           79         let mut cnt = 0;
           80 
           81         for ch in '!'..='~' {
           82             let color = if font.has_char(ch as u8) {
           83                 ui.style().visuals.strong_text_color()
           84             } else {
           85                 ui.style().visuals.text_color()
           86             };
           87             let text: WidgetText = ch.to_string().into();
           88             let font_id = FontId::new(14.0, FontFamily::Monospace);
           89             let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);
           90 
           91             let mut rect = rect.shrink(4.0);
           92             rect.set_top(rect.top() + y);
           93             rect.set_left(rect.left() + x);
           94             x += galley.size().x;
           95             cnt += 1;
           96             if cnt > 31 {
           97                 y += galley.size().y;
           98                 x = 0.;
           99                 cnt = 0;
          100             }
          101             ui.painter()
          102                 .galley_with_override_text_color(egui::Align2::LEFT_TOP.align_size_within_rect(galley.size(), rect).min, galley, color);
          103         }
          104 
          105         #[allow(clippy::map_entry)]
          106         if !self.image_cache.contains_key(&cur_font) {
          107             let buffer = Buffer::new((100, 12));
          108             let mut state = EditState::from_buffer(buffer);
          109 
          110             let text = fl!(crate::LANGUAGE_LOADER, "select-font-dialog-preview-text");
          111             let lowercase = text.to_ascii_lowercase();
          112 
          113             let b = if font.has_char(text.chars().next().unwrap() as u8) {
          114                 text.bytes()
          115             } else {
          116                 lowercase.bytes()
          117             };
          118             for ch in b {
          119                 let opt_size: Option<Size> = font.render(&mut state, ch);
          120                 if let Some(size) = opt_size {
          121                     let mut pos = state.get_caret().get_position();
          122                     pos.x += size.width + font.spaces;
          123                     state.get_caret_mut().set_position(pos);
          124                 }
          125             }
          126             let img = create_image(ui.ctx(), state.get_buffer());
          127             self.image_cache.insert(cur_font, img);
          128         }
          129 
          130         if let Some(image) = self.image_cache.get(&cur_font) {
          131             let sized_texture: SizedTexture = image.into();
          132             let w = (sized_texture.size.x / 2.0).floor();
          133             let h = (sized_texture.size.y / 2.0).floor();
          134             let r = Rect::from_min_size(
          135                 Pos2::new((rect.right() - w - 4.0).floor(), (rect.top() + ((rect.height() - h) / 2.0)).floor()),
          136                 Vec2::new(w, h),
          137             );
          138             let image = Image::from_texture(sized_texture);
          139             image.paint_at(ui, r);
          140             /*
          141             ui.painter().image(
          142                 image.texture_id(ui.ctx()),
          143                 r,
          144                 Rect::from_min_max(Pos2::new(0.0, 0.0), Pos2::new(1.0, 1.0)),
          145                 Color32::WHITE,
          146             );*/
          147 
          148             let font_type = match font.font_type {
          149                 icy_engine::FontType::Outline => {
          150                     fl!(crate::LANGUAGE_LOADER, "select-font-dialog-outline-font")
          151                 }
          152                 icy_engine::FontType::Block => {
          153                     fl!(crate::LANGUAGE_LOADER, "select-font-dialog-block-font")
          154                 }
          155                 icy_engine::FontType::Color => {
          156                     fl!(crate::LANGUAGE_LOADER, "select-font-dialog-color-font")
          157                 }
          158             };
          159 
          160             let font_id = FontId::new(12.0, FontFamily::Proportional);
          161             let text: WidgetText = font_type.into();
          162             let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);
          163 
          164             let rect = Rect::from_min_size(
          165                 Pos2::new((rect.right() - galley.size().x - 10.0).floor(), (rect.top() + 8.0).floor()),
          166                 galley.size(),
          167             );
          168 
          169             ui.painter()
          170                 .rect_filled(rect.expand(2.0), Rounding::same(4.0), ui.style().visuals.widgets.active.bg_fill);
          171 
          172             ui.painter().rect_stroke(rect.expand(2.0), 4.0, Stroke::new(1.0, text_color));
          173 
          174             ui.painter()
          175                 .galley_with_override_text_color(egui::Align2::CENTER_CENTER.align_size_within_rect(galley.size(), rect).min, galley, text_color);
          176         }
          177 
          178         response
          179     }
          180 }
          181 
          182 impl crate::ModalDialog for SelectFontDialog {
          183     fn show(&mut self, ctx: &egui::Context) -> bool {
          184         if let Some(ed) = &mut self.export_dialog {
          185             if ed.show(ctx).selected() {
          186                 if let Some(res) = ed.path() {
          187                     let mut res = res.to_path_buf();
          188                     res.set_extension("tdf");
          189                     if let Some(data) = self.export_data.take() {
          190                         if let Err(err) = fs::write(res, data) {
          191                             log::error!("Failed to write font: {}", err);
          192                         }
          193                     } else {
          194                         log::error!("Export data == None");
          195                     }
          196                 }
          197                 self.export_dialog = None
          198             } else {
          199                 return false;
          200             }
          201         }
          202 
          203         let mut result = false;
          204         let modal = Modal::new(ctx, "select_font_dialog2");
          205         let font_count = self.fonts.lock().len();
          206         modal.show(|ui| {
          207             ui.set_width(700.);
          208             modal.title(ui, fl!(crate::LANGUAGE_LOADER, "select-font-dialog-title", fontcount = font_count));
          209             modal.frame(ui, |ui| {
          210                 let row_height = 200.0 / 2.0;
          211                 ui.horizontal(|ui: &mut egui::Ui| {
          212                     ui.add_sized(
          213                         [250.0, 20.0],
          214                         TextEdit::singleline(&mut self.filter).hint_text(fl!(crate::LANGUAGE_LOADER, "select-font-dialog-filter-text")),
          215                     );
          216                     let response = ui.button("🗙");
          217                     if response.clicked() {
          218                         self.filter.clear();
          219                     }
          220 
          221                     let response = ui.selectable_label(self.show_color, fl!(crate::LANGUAGE_LOADER, "select-font-dialog-color-font"));
          222                     if response.clicked() {
          223                         self.show_color = !self.show_color;
          224                     }
          225 
          226                     let response = ui.selectable_label(self.show_block, fl!(crate::LANGUAGE_LOADER, "select-font-dialog-block-font"));
          227                     if response.clicked() {
          228                         self.show_block = !self.show_block;
          229                     }
          230 
          231                     let response = ui.selectable_label(self.show_outline, fl!(crate::LANGUAGE_LOADER, "select-font-dialog-outline-font"));
          232                     if response.clicked() {
          233                         self.show_outline = !self.show_outline;
          234                     }
          235                 });
          236                 ui.add_space(4.0);
          237 
          238                 let mut filtered_fonts = Vec::new();
          239 
          240                 for i in 0..font_count {
          241                     let font = &self.fonts.lock()[i];
          242                     if font.name.to_lowercase().contains(&self.filter.to_lowercase())
          243                         && (self.show_outline && matches!(font.font_type, icy_engine::FontType::Outline)
          244                             || self.show_block && matches!(font.font_type, icy_engine::FontType::Block)
          245                             || self.show_color && matches!(font.font_type, icy_engine::FontType::Color))
          246                     {
          247                         filtered_fonts.push(i);
          248                     }
          249                 }
          250                 if filtered_fonts.is_empty() {
          251                     if font_count == 0 {
          252                         ui.label(fl!(crate::LANGUAGE_LOADER, "select-font-dialog-no-fonts-installed"));
          253                     } else {
          254                         ui.label(fl!(crate::LANGUAGE_LOADER, "select-font-dialog-no-fonts"));
          255                     }
          256                 } else {
          257                     egui::ScrollArea::vertical()
          258                         .max_height(300.)
          259                         .show_rows(ui, row_height, filtered_fonts.len(), |ui, range| {
          260                             for row in range {
          261                                 let is_selected = self.selected_font == filtered_fonts[row] as i32;
          262                                 let response = self.draw_font_row(ui, filtered_fonts[row], row_height, is_selected);
          263 
          264                                 if response.clicked() {
          265                                     self.selected_font = filtered_fonts[row] as i32;
          266                                 }
          267                                 if response.double_clicked() {
          268                                     self.selected_font = filtered_fonts[row] as i32;
          269                                     self.do_select = true;
          270                                     result = true;
          271                                 }
          272                             }
          273                         });
          274                 }
          275             });
          276 
          277             modal.buttons(ui, |ui| {
          278                 if ui.button(fl!(crate::LANGUAGE_LOADER, "select-font-dialog-select")).clicked() {
          279                     self.do_select = true;
          280                     result = true;
          281                 }
          282                 if ui.button(fl!(crate::LANGUAGE_LOADER, "new-file-cancel")).clicked() {
          283                     result = true;
          284                 }
          285 
          286                 if ui.button(fl!(crate::LANGUAGE_LOADER, "export-button-title")).clicked() {
          287                     match self.fonts.lock()[self.selected_font as usize].as_tdf_bytes() {
          288                         Ok(data) => {
          289                             let mut initial_path = None;
          290                             crate::set_default_initial_directory_opt(&mut initial_path);
          291                             let mut dialog = FileDialog::save_file(initial_path);
          292                             dialog.open();
          293                             self.export_data = Some(data);
          294                             self.export_dialog = Some(dialog);
          295                         }
          296                         Err(err) => {
          297                             log::error!("Failed to export font: {}", err);
          298                         }
          299                     }
          300                 }
          301             });
          302         });
          303         modal.open();
          304         result
          305     }
          306 
          307     fn should_commit(&self) -> bool {
          308         self.do_select
          309     }
          310 
          311     fn commit_self(&self, _window: &mut MainWindow<'_>) -> crate::TerminalResult<Option<Message>> {
          312         *self.selected_font_arc.lock() = self.selected_font;
          313         Ok(None)
          314     }
          315 }
          316 
          317 pub fn create_image(ctx: &egui::Context, buf: &Buffer) -> TextureHandle {
          318     let (size, pixels) = buf.render_to_rgba(Rectangle::from(0, 0, buf.get_width(), buf.get_height()));
          319     let color_image: ColorImage = ColorImage::from_rgba_premultiplied([size.width as usize, size.height as usize], &pixels);
          320     ctx.load_texture("my_texture", color_image, TextureOptions::NEAREST)
          321 }