select_palette_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_palette_dialog.rs (16314B)
       ---
            1 use std::{fs, io::Read, path::Path};
            2 
            3 use eframe::{
            4     egui::{self, Response, Sense, TextEdit, WidgetText},
            5     epaint::{Color32, FontFamily, FontId, Pos2, Rect, Rounding, Stroke, Vec2},
            6 };
            7 use egui_file::FileDialog;
            8 use egui_modal::Modal;
            9 use i18n_embed_fl::fl;
           10 use icy_engine::{Palette, PaletteFormat, PaletteMode, C64_DEFAULT_PALETTE, DOS_DEFAULT_PALETTE, EGA_PALETTE, VIEWDATA_PALETTE, XTERM_256_PALETTE};
           11 use walkdir::WalkDir;
           12 
           13 use crate::{to_message, AnsiEditor, Message, Settings, TerminalResult};
           14 
           15 enum PaletteSource {
           16     BuiltIn,
           17     Library,
           18     File,
           19 }
           20 
           21 pub struct SelectPaletteDialog {
           22     palettes: Vec<(Palette, PaletteSource)>,
           23     selected_palette: i32,
           24 
           25     filter: String,
           26     show_builtin: bool,
           27     show_library: bool,
           28     show_file: bool,
           29 
           30     do_select: bool,
           31     edit_selected_font: bool,
           32 
           33     export_dialog: Option<FileDialog>,
           34 }
           35 
           36 impl SelectPaletteDialog {
           37     pub fn new(editor: &AnsiEditor) -> anyhow::Result<Self> {
           38         let mut palettes = Vec::new();
           39         let mode = editor.buffer_view.lock().get_buffer().palette_mode;
           40 
           41         let mut dos = Palette::from_slice(&DOS_DEFAULT_PALETTE);
           42         dos.title = fl!(crate::LANGUAGE_LOADER, "palette_selector-dos_default_palette");
           43         add_palette(&mut palettes, mode, (dos, PaletteSource::BuiltIn));
           44 
           45         let mut dos = Palette::from_slice(&DOS_DEFAULT_PALETTE[0..8]);
           46         dos.title = fl!(crate::LANGUAGE_LOADER, "palette_selector-dos_default_low_palette");
           47         add_palette(&mut palettes, mode, (dos, PaletteSource::BuiltIn));
           48 
           49         let mut dos = Palette::from_slice(&C64_DEFAULT_PALETTE);
           50         dos.title = fl!(crate::LANGUAGE_LOADER, "palette_selector-c64_default_palette");
           51         add_palette(&mut palettes, mode, (dos, PaletteSource::BuiltIn));
           52 
           53         let mut dos = Palette::from_slice(&EGA_PALETTE);
           54         dos.title = fl!(crate::LANGUAGE_LOADER, "palette_selector-ega_default_palette");
           55         add_palette(&mut palettes, mode, (dos, PaletteSource::BuiltIn));
           56 
           57         let mut dos = Palette::from_slice(&XTERM_256_PALETTE.map(|p| {
           58             let mut col = p.1.clone();
           59             col.name = Some(p.0.to_string());
           60             col
           61         }));
           62         dos.title = fl!(crate::LANGUAGE_LOADER, "palette_selector-xterm_default_palette");
           63         add_palette(&mut palettes, mode, (dos, PaletteSource::BuiltIn));
           64 
           65         let mut dos = Palette::from_slice(&VIEWDATA_PALETTE[0..8]);
           66         dos.title = fl!(crate::LANGUAGE_LOADER, "palette_selector-viewdata_default_palette");
           67         add_palette(&mut palettes, mode, (dos, PaletteSource::BuiltIn));
           68 
           69         let mut pal = editor.buffer_view.lock().get_buffer().palette.clone();
           70         let mut selected_palette = 0;
           71         if let Some(idx) = palettes.iter().position(|p| p.0.are_colors_equal(&pal)) {
           72             selected_palette = idx as i32;
           73         } else {
           74             if pal.title.is_empty() {
           75                 pal.title = fl!(crate::LANGUAGE_LOADER, "palette_selector-extracted_from_buffer_default_label");
           76             }
           77             palettes.insert(0, (pal, PaletteSource::File));
           78         }
           79         if let Ok(palette_dir) = Settings::get_palettes_diretory() {
           80             for palette in SelectPaletteDialog::load_palettes(palette_dir.as_path(), mode)? {
           81                 add_palette(&mut palettes, mode, palette);
           82             }
           83         }
           84 
           85         Ok(Self {
           86             do_select: false,
           87             palettes,
           88             selected_palette,
           89             filter: String::new(),
           90             show_builtin: true,
           91             show_library: true,
           92             show_file: true,
           93             edit_selected_font: false,
           94             export_dialog: None,
           95         })
           96     }
           97 
           98     fn load_palettes(tdf_dir: &Path, mode: PaletteMode) -> anyhow::Result<Vec<(Palette, PaletteSource)>> {
           99         let mut palettes = Vec::new();
          100         let walker = WalkDir::new(tdf_dir).into_iter();
          101         for entry in walker.filter_entry(|e| !crate::model::font_imp::FontTool::is_hidden(e)) {
          102             if let Err(e) = entry {
          103                 log::error!("Can't load palette library: {e}");
          104                 break;
          105             }
          106             let Ok(entry) = entry else {
          107                 continue;
          108             };
          109             let path = entry.path();
          110 
          111             if path.is_dir() {
          112                 continue;
          113             }
          114             let Some(extension) = path.extension() else {
          115                 continue;
          116             };
          117             let Some(extension) = extension.to_str() else {
          118                 continue;
          119             };
          120 
          121             if let Ok(palette) = Palette::import_palette(path, &fs::read(path)?) {
          122                 add_palette(&mut palettes, mode, (palette, PaletteSource::Library));
          123             }
          124             let ext = extension.to_lowercase();
          125             if ext == "zip" {
          126                 match fs::File::open(path) {
          127                     Ok(mut file) => {
          128                         let mut data = Vec::new();
          129                         file.read_to_end(&mut data).unwrap_or_default();
          130                         SelectPaletteDialog::read_zip_archive(data, &mut palettes, mode);
          131                     }
          132 
          133                     Err(err) => {
          134                         log::error!("Failed to open zip file: {}", err);
          135                         return Err(err.into());
          136                     }
          137                 }
          138             }
          139         }
          140         Ok(palettes)
          141     }
          142 
          143     fn read_zip_archive(data: Vec<u8>, palettes: &mut Vec<(Palette, PaletteSource)>, mode: PaletteMode) {
          144         let file = std::io::Cursor::new(data);
          145         match zip::ZipArchive::new(file) {
          146             Ok(mut archive) => {
          147                 for i in 0..archive.len() {
          148                     match archive.by_index(i) {
          149                         Ok(mut file) => {
          150                             if let Some(name) = file.enclosed_name() {
          151                                 let file_name_buf = name.to_path_buf();
          152                                 let file_name = file_name_buf.to_string_lossy().to_ascii_lowercase();
          153                                 let mut data = Vec::new();
          154                                 file.read_to_end(&mut data).unwrap_or_default();
          155 
          156                                 if file_name.ends_with(".zip") {
          157                                     SelectPaletteDialog::read_zip_archive(data, palettes, mode);
          158                                 } else if let Ok(palette) = Palette::import_palette(&file_name_buf, &data) {
          159                                     add_palette(palettes, mode, (palette, PaletteSource::Library));
          160                                 }
          161                             }
          162                         }
          163                         Err(err) => {
          164                             log::error!("Error reading zip file: {}", err);
          165                         }
          166                     }
          167                 }
          168             }
          169             Err(err) => {
          170                 log::error!("Error reading zip archive: {}", err);
          171             }
          172         }
          173     }
          174 
          175     pub fn draw_palette_row(&mut self, ui: &mut egui::Ui, cur_pal: usize, row_height: f32, is_selected: bool) -> Response {
          176         let palette = &self.palettes[cur_pal];
          177         let (id, rect) = ui.allocate_space([ui.available_width(), row_height].into());
          178         let response = ui.interact(rect, id, Sense::click());
          179 
          180         if response.hovered() {
          181             ui.painter()
          182                 .rect_filled(rect.expand(1.0), Rounding::same(4.0), ui.style().visuals.widgets.active.bg_fill);
          183         } else if is_selected {
          184             ui.painter()
          185                 .rect_filled(rect.expand(1.0), Rounding::same(4.0), ui.style().visuals.extreme_bg_color);
          186         }
          187 
          188         let text_color = if is_selected {
          189             ui.style().visuals.strong_text_color()
          190         } else {
          191             ui.style().visuals.text_color()
          192         };
          193 
          194         let font_id = FontId::new(14.0, FontFamily::Proportional);
          195         let text: WidgetText = palette.0.title.clone().into();
          196         let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);
          197         ui.painter().galley_with_override_text_color(
          198             egui::Align2::LEFT_TOP.align_size_within_rect(galley.size(), rect.shrink(4.0)).min,
          199             galley,
          200             text_color,
          201         );
          202 
          203         let mut color_rect = rect;
          204         color_rect.set_top(rect.top() + 22.0);
          205         color_rect.set_height(rect.height() - 32.0);
          206 
          207         let mut num_colors = 8;
          208         while (palette.0.len() as f32 / num_colors as f32).ceil() > 6.0 {
          209             num_colors += 8;
          210         }
          211         // paint palette preview
          212         let w = color_rect.width() / num_colors as f32;
          213         let h = color_rect.height() / (palette.0.len() as f32 / num_colors as f32).ceil().max(1.0);
          214 
          215         for i in 0..palette.0.len() {
          216             let (r, g, b) = palette.0.get_rgb(i as u32);
          217             let rect = Rect::from_min_size(
          218                 Pos2::new(color_rect.left() + (i % num_colors) as f32 * w, color_rect.top() + (i / num_colors) as f32 * h),
          219                 Vec2::new(w, h),
          220             );
          221             ui.painter().rect_filled(rect, Rounding::ZERO, Color32::from_rgb(r, g, b));
          222         }
          223 
          224         // paint palette tag
          225         let font_type = match palette.1 {
          226             PaletteSource::BuiltIn => {
          227                 fl!(crate::LANGUAGE_LOADER, "select-palette-dialog-builtin_palette")
          228             }
          229             PaletteSource::Library => {
          230                 fl!(crate::LANGUAGE_LOADER, "font_selector-library_font")
          231             }
          232             PaletteSource::File => {
          233                 fl!(crate::LANGUAGE_LOADER, "font_selector-file_font")
          234             }
          235         };
          236 
          237         let font_id = FontId::new(12.0, FontFamily::Proportional);
          238         let text: WidgetText = font_type.into();
          239         let galley = text.into_galley(ui, Some(false), f32::INFINITY, font_id);
          240 
          241         let rect = Rect::from_min_size(
          242             Pos2::new((rect.right() - galley.size().x - 10.0).floor(), (rect.top() + 8.0).floor()),
          243             galley.size(),
          244         );
          245         ui.painter()
          246             .rect_filled(rect.expand(2.0), Rounding::same(4.0), ui.style().visuals.widgets.active.bg_fill);
          247 
          248         ui.painter().rect_stroke(rect.expand(2.0), 4.0, Stroke::new(1.0, text_color));
          249 
          250         ui.painter()
          251             .galley_with_override_text_color(egui::Align2::CENTER_CENTER.align_size_within_rect(galley.size(), rect).min, galley, text_color);
          252 
          253         if palette.0.description.is_empty() {
          254             response
          255         } else {
          256             response.on_hover_ui(|ui| {
          257                 ui.small(palette.0.description.clone());
          258             })
          259         }
          260     }
          261 }
          262 
          263 fn add_palette(palettes: &mut Vec<(Palette, PaletteSource)>, mode: icy_engine::PaletteMode, mut palette: (Palette, PaletteSource)) {
          264     match mode {
          265         icy_engine::PaletteMode::RGB => {}
          266         icy_engine::PaletteMode::Free16 | icy_engine::PaletteMode::Fixed16 => palette.0.resize(16),
          267         icy_engine::PaletteMode::Free8 => palette.0.resize(8),
          268     };
          269     palettes.push(palette);
          270 }
          271 
          272 impl crate::ModalDialog for SelectPaletteDialog {
          273     fn show(&mut self, ctx: &egui::Context) -> bool {
          274         if let Some(ed) = &mut self.export_dialog {
          275             if ed.show(ctx).selected() {
          276                 if let Some(res) = ed.path() {
          277                     let s = self.selected_palette as usize;
          278                     if s < self.palettes.len() {
          279                         let res = res.with_extension("gpl");
          280                         let data = self.palettes[s].0.export_palette(&PaletteFormat::Gpl);
          281                         if let Err(err) = fs::write(res, data) {
          282                             log::error!("Error exporting palette: {err}");
          283                         }
          284                     }
          285                 }
          286                 self.export_dialog = None
          287             } else {
          288                 return false;
          289             }
          290         }
          291 
          292         let mut result = false;
          293         let modal = Modal::new(ctx, "select_font_dialog2");
          294         let palette_count = self.palettes.len();
          295         modal.show(|ui| {
          296             modal.title(ui, fl!(crate::LANGUAGE_LOADER, "select-palette-dialog-title", count = palette_count));
          297             modal.frame(ui, |ui| {
          298                 let row_height = 200.0 / 2.0;
          299                 ui.horizontal(|ui: &mut egui::Ui| {
          300                     ui.add_sized(
          301                         [250.0, 20.0],
          302                         TextEdit::singleline(&mut self.filter).hint_text(fl!(crate::LANGUAGE_LOADER, "select-font-dialog-filter-text")),
          303                     );
          304                     let response = ui.button("🗙");
          305                     if response.clicked() {
          306                         self.filter.clear();
          307                     }
          308 
          309                     let response = ui.selectable_label(self.show_library, fl!(crate::LANGUAGE_LOADER, "font_selector-library_font"));
          310                     if response.clicked() {
          311                         self.show_library = !self.show_library;
          312                     }
          313 
          314                     let response = ui.selectable_label(self.show_builtin, fl!(crate::LANGUAGE_LOADER, "select-palette-dialog-builtin_palette"));
          315                     if response.clicked() {
          316                         self.show_builtin = !self.show_builtin;
          317                     }
          318                 });
          319                 ui.add_space(4.0);
          320 
          321                 let mut filtered_fonts = Vec::new();
          322 
          323                 for i in 0..palette_count {
          324                     let palette = &self.palettes[i];
          325                     let match_filter = match palette.1 {
          326                         PaletteSource::BuiltIn => self.show_builtin,
          327                         PaletteSource::Library => self.show_library,
          328                         PaletteSource::File => self.show_file,
          329                     };
          330 
          331                     if palette.0.title.to_lowercase().contains(&self.filter.to_lowercase()) && match_filter {
          332                         filtered_fonts.push(i);
          333                     }
          334                 }
          335                 if filtered_fonts.is_empty() {
          336                     if palette_count == 0 {
          337                         ui.label(fl!(crate::LANGUAGE_LOADER, "select-font-dialog-no-fonts-installed"));
          338                     } else {
          339                         ui.label(fl!(crate::LANGUAGE_LOADER, "select-palette-dialog-no-matching-palettes"));
          340                     }
          341                 } else {
          342                     egui::ScrollArea::vertical()
          343                         .max_height(300.)
          344                         .show_rows(ui, row_height, filtered_fonts.len(), |ui, range| {
          345                             for row in range {
          346                                 let is_selected = self.selected_palette == filtered_fonts[row] as i32;
          347                                 let response = self.draw_palette_row(ui, filtered_fonts[row], row_height, is_selected);
          348 
          349                                 if response.clicked() {
          350                                     self.selected_palette = filtered_fonts[row] as i32;
          351                                 }
          352                                 if response.double_clicked() {
          353                                     self.selected_palette = filtered_fonts[row] as i32;
          354                                     self.do_select = true;
          355                                     result = true;
          356                                 }
          357                             }
          358                         });
          359                 }
          360             });
          361 
          362             modal.buttons(ui, |ui| {
          363                 if ui.button(fl!(crate::LANGUAGE_LOADER, "select-font-dialog-select")).clicked() {
          364                     self.do_select = true;
          365                     result = true;
          366                 }
          367                 if ui.button(fl!(crate::LANGUAGE_LOADER, "new-file-cancel")).clicked() {
          368                     result = true;
          369                 }
          370 
          371                 if ui.button(fl!(crate::LANGUAGE_LOADER, "export-button-title")).clicked() {
          372                     let mut initial_path = None;
          373                     crate::set_default_initial_directory_opt(&mut initial_path);
          374                     let mut dialog = FileDialog::save_file(initial_path);
          375                     dialog.open();
          376                     self.export_dialog = Some(dialog);
          377                 }
          378             });
          379         });
          380         modal.open();
          381         result
          382     }
          383 
          384     fn should_commit(&self) -> bool {
          385         self.do_select || self.edit_selected_font
          386     }
          387 
          388     fn commit(&self, editor: &mut AnsiEditor) -> TerminalResult<Option<Message>> {
          389         if let Some((palette, _)) = self.palettes.get(self.selected_palette as usize) {
          390             Ok(to_message(editor.buffer_view.lock().get_edit_state_mut().switch_to_palette(palette.clone())))
          391         } else {
          392             Ok(None)
          393         }
          394     }
          395 }