commands.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
       ---
       commands.rs (19704B)
       ---
            1 use std::collections::HashMap;
            2 
            3 use eframe::egui::{self, Modifiers};
            4 use egui_bind::{BindTarget, KeyOrPointer};
            5 use i18n_embed_fl::fl;
            6 use icy_engine::PaletteMode;
            7 
            8 use crate::{button_with_shortcut, DocumentTab, Message, MRU_FILES, SETTINGS};
            9 
           10 pub trait CommandState {
           11     fn is_enabled(&self, _open_tab_opt: Option<&DocumentTab>) -> bool {
           12         true
           13     }
           14     fn is_checked(&self, _open_tab_opt: Option<&DocumentTab>) -> Option<bool> {
           15         None
           16     }
           17 }
           18 
           19 #[derive(Default)]
           20 pub struct AlwaysEnabledState {}
           21 impl CommandState for AlwaysEnabledState {}
           22 
           23 #[derive(Default)]
           24 pub struct BufferOpenState {}
           25 
           26 impl CommandState for BufferOpenState {
           27     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
           28         if let Some(pane) = open_tab_opt {
           29             return pane.doc.lock().get_ansi_editor().is_some();
           30         }
           31         false
           32     }
           33 }
           34 
           35 #[derive(Default)]
           36 pub struct CanSwitchPaletteState {}
           37 
           38 impl CommandState for CanSwitchPaletteState {
           39     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
           40         if let Some(pane) = open_tab_opt {
           41             if let Some(editor) = pane.doc.lock().get_ansi_editor() {
           42                 return !matches!(editor.buffer_view.lock().get_buffer().palette_mode, PaletteMode::Fixed16);
           43             }
           44         }
           45         false
           46     }
           47 }
           48 
           49 #[derive(Default)]
           50 pub struct LayerBordersState {}
           51 
           52 impl CommandState for LayerBordersState {
           53     fn is_enabled(&self, _open_tab_opt: Option<&DocumentTab>) -> bool {
           54         true
           55     }
           56 
           57     fn is_checked(&self, _open_tab_opt: Option<&DocumentTab>) -> Option<bool> {
           58         unsafe { Some(SETTINGS.show_layer_borders) }
           59     }
           60 }
           61 
           62 #[derive(Default)]
           63 pub struct LineNumberState {}
           64 
           65 impl CommandState for LineNumberState {
           66     fn is_enabled(&self, _open_tab_opt: Option<&DocumentTab>) -> bool {
           67         true
           68     }
           69 
           70     fn is_checked(&self, _open_tab_opt: Option<&DocumentTab>) -> Option<bool> {
           71         unsafe { Some(SETTINGS.show_line_numbers) }
           72     }
           73 }
           74 
           75 #[derive(Default)]
           76 pub struct FileOpenState {}
           77 
           78 impl CommandState for FileOpenState {
           79     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
           80         open_tab_opt.is_some()
           81     }
           82 }
           83 
           84 #[derive(Default)]
           85 pub struct FileIsDirtyState {}
           86 
           87 impl CommandState for FileIsDirtyState {
           88     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
           89         if let Some(pane) = open_tab_opt {
           90             pane.is_dirty()
           91         } else {
           92             false
           93         }
           94     }
           95 }
           96 
           97 #[derive(Default)]
           98 pub struct HasRecentFilesState {}
           99 
          100 impl CommandState for HasRecentFilesState {
          101     fn is_enabled(&self, _open_tab_opt: Option<&DocumentTab>) -> bool {
          102         unsafe { !MRU_FILES.get_recent_files().is_empty() }
          103     }
          104 }
          105 
          106 #[derive(Default)]
          107 pub struct CanUndoState {}
          108 
          109 impl CommandState for CanUndoState {
          110     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
          111         if let Some(pane) = open_tab_opt {
          112             return pane.doc.lock().can_undo();
          113         }
          114         false
          115     }
          116 }
          117 #[derive(Default)]
          118 pub struct CanRedoState {}
          119 
          120 impl CommandState for CanRedoState {
          121     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
          122         if let Some(pane) = open_tab_opt {
          123             return pane.doc.lock().can_redo();
          124         }
          125         false
          126     }
          127 }
          128 
          129 #[derive(Default)]
          130 pub struct CanCutState {}
          131 
          132 impl CommandState for CanCutState {
          133     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
          134         if let Some(pane) = open_tab_opt {
          135             return pane.doc.lock().can_cut();
          136         }
          137         false
          138     }
          139 }
          140 
          141 #[derive(Default)]
          142 pub struct CanCopyState {}
          143 
          144 impl CommandState for CanCopyState {
          145     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
          146         if let Some(pane) = open_tab_opt {
          147             return pane.doc.lock().can_copy();
          148         }
          149         false
          150     }
          151 }
          152 
          153 #[derive(Default)]
          154 pub struct CanPasteState {}
          155 
          156 impl CommandState for CanPasteState {
          157     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
          158         if let Some(pane) = open_tab_opt {
          159             return pane.doc.lock().can_paste();
          160         }
          161         false
          162     }
          163 }
          164 
          165 #[derive(Default)]
          166 pub struct LGAFontState {}
          167 
          168 impl CommandState for LGAFontState {
          169     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
          170         if let Some(pane) = open_tab_opt {
          171             return pane.doc.lock().get_ansi_editor().is_some();
          172         }
          173         false
          174     }
          175     fn is_checked(&self, open_tab_opt: Option<&DocumentTab>) -> Option<bool> {
          176         if let Some(pane) = open_tab_opt {
          177             if let Some(editor) = pane.doc.lock().get_ansi_editor() {
          178                 return Some(editor.buffer_view.lock().get_buffer().use_letter_spacing());
          179             }
          180         }
          181         Some(false)
          182     }
          183 }
          184 #[derive(Default)]
          185 pub struct AspectRatioState {}
          186 
          187 impl CommandState for AspectRatioState {
          188     fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
          189         if let Some(pane) = open_tab_opt {
          190             return pane.doc.lock().get_ansi_editor().is_some();
          191         }
          192         false
          193     }
          194     fn is_checked(&self, open_tab_opt: Option<&DocumentTab>) -> Option<bool> {
          195         if let Some(pane) = open_tab_opt {
          196             if let Some(editor) = pane.doc.lock().get_ansi_editor() {
          197                 return Some(editor.buffer_view.lock().get_buffer().use_aspect_ratio());
          198             }
          199         }
          200         Some(false)
          201     }
          202 }
          203 
          204 pub struct CommandWrapper {
          205     key: Option<(KeyOrPointer, Modifiers)>,
          206     message: Message,
          207     label: String,
          208     pub is_enabled: bool,
          209     pub is_checked: Option<bool>,
          210     state_key: u32,
          211 }
          212 
          213 mod modifier_keys {
          214     use eframe::egui::Modifiers;
          215 
          216     pub const NONE: Modifiers = Modifiers {
          217         alt: false,
          218         ctrl: false,
          219         shift: false,
          220         mac_cmd: false,
          221         command: false,
          222     };
          223 
          224     pub const CTRL: Modifiers = Modifiers {
          225         alt: false,
          226         ctrl: true,
          227         shift: false,
          228         mac_cmd: false,
          229         command: false,
          230     };
          231 
          232     pub const ALT: Modifiers = Modifiers {
          233         alt: true,
          234         ctrl: false,
          235         shift: false,
          236         mac_cmd: false,
          237         command: false,
          238     };
          239 
          240     pub const ALT_CTRL: Modifiers = Modifiers {
          241         alt: true,
          242         ctrl: true,
          243         shift: false,
          244         mac_cmd: false,
          245         command: false,
          246     };
          247 
          248     pub const CTRL_SHIFT: Modifiers = Modifiers {
          249         alt: false,
          250         ctrl: true,
          251         shift: true,
          252         mac_cmd: false,
          253         command: false,
          254     };
          255 }
          256 
          257 macro_rules! key {
          258     () => {
          259         None
          260     };
          261     ($key:ident, $modifier: ident) => {
          262         Some((egui::Key::$key, modifier_keys::$modifier))
          263     };
          264 }
          265 
          266 macro_rules! keys {
          267     ($( ($l:ident, $translation: expr, $message:ident, $cmd_state: ident$(, $key:ident, $modifier: ident)? ) ),* $(,)? ) => {
          268 
          269         pub struct Commands {
          270             state_map: HashMap<u32, Box<dyn CommandState>>,
          271             $(
          272                 pub $l: CommandWrapper,
          273             )*
          274         }
          275 
          276         impl Default for Commands {
          277             fn default() -> Self {
          278                 let mut state_map = HashMap::<u32, Box<dyn CommandState>>::new();
          279                 $(
          280                     state_map.insert(hash(stringify!($cmd_state)), Box::<$cmd_state>::default());
          281                 )*
          282 
          283                 Self {
          284                     state_map,
          285                     $(
          286                         $l: CommandWrapper::new(key!($($key, $modifier)?), Message::$message, fl!(crate::LANGUAGE_LOADER, $translation), hash(stringify!($cmd_state))),
          287                     )*
          288                 }
          289             }
          290         }
          291 
          292         impl Commands {
          293             pub fn default_keybindings() -> Vec<(String, egui::Key, Modifiers)> {
          294                 let mut result = Vec::new();
          295                 $(
          296                     let key = key!($($key, $modifier)?);
          297                     if let Some((key, modifier)) = key  {
          298                         result.push((stringify!($l).to_string(), key, modifier));
          299                     }
          300                 )*
          301                 result
          302             }
          303             pub fn check(&self, ctx: &egui::Context, message: &mut Option<Message>) {
          304                 $(
          305                     if self.$l.is_pressed(ctx) {
          306                         *message = Some(self.$l.message.clone());
          307                         return;
          308                     }
          309                 )*
          310             }
          311 
          312             pub fn update_states(&mut self, open_tab_opt: Option<&DocumentTab>) {
          313                 let mut result_map = HashMap::new();
          314                 for (k, v) in &self.state_map {
          315                     let is_enabled = v.is_enabled(open_tab_opt);
          316                     let is_checked = v.is_checked(open_tab_opt);
          317                     result_map.insert(k, (is_enabled, is_checked));
          318                 }
          319 
          320                 $(
          321                     self.$l.update_state(&result_map);
          322                 )*
          323             }
          324 
          325             pub fn apply_key_bindings(&mut self, key_bindings: &Vec<(String, egui::Key, Modifiers)> ) {
          326                 for (binding, key, modifier) in key_bindings {
          327                     match binding.as_str() {
          328                         $(
          329                             stringify!($l) => {
          330                                 self.$l.key = Some((KeyOrPointer::Key(*key), *modifier));
          331                             }
          332                         )*
          333 
          334                         _ => {}
          335                     }
          336                 }
          337             }
          338 
          339             pub(crate) fn show_keybinds_settings(ui: &mut egui::Ui, filter: &mut String, keys: &mut HashMap<String, (egui::Key, Modifiers)>) -> bool {
          340                 let mut changed_bindings = false;
          341 
          342                 ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
          343                     let response = ui.button("🗙");
          344                     if response.clicked() {
          345                         filter.clear();
          346                     }
          347 
          348                     ui.add(
          349                         egui::TextEdit::singleline(filter).hint_text(fl!(
          350                             crate::LANGUAGE_LOADER,
          351                             "settings-key_filter_preview_text"
          352                         )),
          353                     );
          354                 });
          355                 egui::ScrollArea::vertical()
          356                     .max_height(240.0)
          357                     .show(ui, |ui| {
          358 
          359                     $(
          360                         let label = fl!(crate::LANGUAGE_LOADER, $translation);
          361                         if filter.is_empty() || label.to_lowercase().contains(filter.to_lowercase().as_str()) {
          362                             ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
          363                                 let mut bind = if let Some(x) = keys.get(stringify!($l)) { Some(x.clone ()) } else {None };
          364                                 if ui.add(egui_bind::Bind::new(
          365                                     stringify!($l).to_string(),
          366                                     &mut bind,
          367                                 )).changed() {
          368                                     if let Some(bind) = bind {
          369                                         keys.insert(stringify!($l).into(), bind);
          370                                     }  else {
          371                                         keys.remove(stringify!($l));
          372                                     }
          373                                     changed_bindings = true;
          374                                 }
          375                                 ui.label(label);
          376                             });
          377                         }
          378                     )*
          379                 });
          380                 changed_bindings
          381             }
          382 
          383         }
          384     };
          385 }
          386 
          387 fn hash(str: impl Into<String>) -> u32 {
          388     use std::collections::hash_map::DefaultHasher;
          389     use std::hash::{Hash, Hasher};
          390 
          391     let mut hasher = DefaultHasher::new();
          392     str.into().hash(&mut hasher);
          393     hasher.finish() as u32
          394 }
          395 
          396 impl CommandWrapper {
          397     pub fn new(key: Option<(egui::Key, Modifiers)>, message: Message, description: String, state_key: u32) -> Self {
          398         let key = key.map(|(k, m)| (KeyOrPointer::Key(k), m));
          399         Self {
          400             key,
          401             message,
          402             label: description,
          403             state_key,
          404             is_enabled: true,
          405             is_checked: None,
          406         }
          407     }
          408 
          409     pub fn update_state(&mut self, result_map: &HashMap<&u32, (bool, Option<bool>)>) {
          410         if let Some((is_enabled, is_checked)) = result_map.get(&self.state_key) {
          411             self.is_enabled = *is_enabled;
          412             self.is_checked = *is_checked;
          413         }
          414     }
          415 
          416     pub fn is_pressed(&self, ctx: &egui::Context) -> bool {
          417         self.key.pressed(ctx)
          418     }
          419 
          420     pub fn ui(&self, ui: &mut egui::Ui, message: &mut Option<Message>) {
          421         if let Some(mut checked) = self.is_checked {
          422             if ui.add(egui::Checkbox::new(&mut checked, &self.label)).clicked() {
          423                 *message = Some(self.message.clone());
          424                 ui.close_menu();
          425             }
          426             return;
          427         }
          428 
          429         let response = ui.with_layout(ui.layout().with_cross_justify(true), |ui| {
          430             ui.set_enabled(self.is_enabled);
          431             if let Some((KeyOrPointer::Key(k), modifier)) = self.key {
          432                 let mut shortcut = k.name().to_string();
          433 
          434                 if modifier.ctrl {
          435                     shortcut.insert_str(0, "Ctrl+");
          436                 }
          437 
          438                 if modifier.alt {
          439                     shortcut.insert_str(0, "Alt+");
          440                 }
          441 
          442                 if modifier.shift {
          443                     shortcut.insert_str(0, "Shift+");
          444                 }
          445 
          446                 button_with_shortcut(ui, true, &self.label, shortcut)
          447             } else {
          448                 ui.add(egui::Button::new(&self.label).wrap(false))
          449             }
          450         });
          451 
          452         if response.inner.clicked() {
          453             *message = Some(self.message.clone());
          454             ui.close_menu();
          455         }
          456     }
          457 }
          458 
          459 keys![
          460     (new_file, "menu-new", NewFileDialog, AlwaysEnabledState, N, CTRL),
          461     (save, "menu-save", SaveFile, FileIsDirtyState, S, CTRL),
          462     (save_as, "menu-save-as", SaveFileAs, FileOpenState, S, CTRL_SHIFT),
          463     (open_file, "menu-open", OpenFileDialog, AlwaysEnabledState, O, CTRL),
          464     (export, "menu-export", ExportFile, BufferOpenState),
          465     (edit_font_outline, "menu-edit-font-outline", ShowOutlineDialog, AlwaysEnabledState),
          466     (close_window, "menu-close", CloseWindow, AlwaysEnabledState, Q, CTRL),
          467     (undo, "menu-undo", Undo, CanUndoState, Z, CTRL),
          468     (redo, "menu-redo", Redo, CanRedoState, Z, CTRL_SHIFT),
          469     (cut, "menu-cut", Cut, CanCutState, X, CTRL),
          470     (copy, "menu-copy", Copy, CanCopyState, C, CTRL),
          471     (paste, "menu-paste", Paste, CanPasteState, V, CTRL),
          472     (show_settings, "menu-show_settings", ShowSettings, AlwaysEnabledState),
          473     (select_all, "menu-select-all", SelectAll, BufferOpenState, A, CTRL),
          474     (deselect, "menu-select_nothing", SelectNothing, BufferOpenState),
          475     (erase_selection, "menu-erase", DeleteSelection, BufferOpenState, Delete, NONE),
          476     (flip_x, "menu-flipx", FlipX, BufferOpenState),
          477     (flip_y, "menu-flipy", FlipY, BufferOpenState),
          478     (justifycenter, "menu-justifycenter", Center, BufferOpenState),
          479     (justifyleft, "menu-justifyleft", JustifyLeft, BufferOpenState),
          480     (justifyright, "menu-justifyright", JustifyRight, BufferOpenState),
          481     (crop, "menu-crop", Crop, BufferOpenState),
          482     (about, "menu-about", ShowAboutDialog, AlwaysEnabledState),
          483     (justify_line_center, "menu-justify_line_center", CenterLine, BufferOpenState, C, ALT),
          484     (justify_line_left, "menu-justify_line_left", JustifyLineLeft, BufferOpenState, L, ALT),
          485     (justify_line_right, "menu-justify_line_right", JustifyLineRight, BufferOpenState, R, ALT),
          486     (insert_row, "menu-insert_row", InsertRow, BufferOpenState, ArrowUp, ALT),
          487     (delete_row, "menu-delete_row", DeleteRow, BufferOpenState, ArrowDown, ALT),
          488     (insert_column, "menu-insert_colum", InsertColumn, BufferOpenState, ArrowRight, ALT),
          489     (delete_column, "menu-delete_colum", DeleteColumn, BufferOpenState, ArrowLeft, ALT),
          490     (erase_row, "menu-erase_row", EraseRow, BufferOpenState, E, ALT),
          491     (erase_row_to_start, "menu-erase_row_to_start", EraseRowToStart, BufferOpenState, Home, ALT),
          492     (erase_row_to_end, "menu-erase_row_to_end", EraseRowToEnd, BufferOpenState, End, ALT),
          493     (erase_column, "menu-erase_column", EraseColumn, BufferOpenState, E, ALT),
          494     (
          495         erase_column_to_start,
          496         "menu-erase_column_to_start",
          497         EraseColumnToStart,
          498         BufferOpenState,
          499         Home,
          500         ALT
          501     ),
          502     (erase_column_to_end, "menu-erase_column_to_end", EraseColumnToEnd, BufferOpenState, End, ALT),
          503     (scroll_area_up, "menu-scroll_area_up", ScrollAreaUp, BufferOpenState, ArrowUp, ALT_CTRL),
          504     (scroll_area_down, "menu-scroll_area_down", ScrollAreaDown, BufferOpenState, ArrowDown, ALT_CTRL),
          505     (scroll_area_left, "menu-scroll_area_left", ScrollAreaLeft, BufferOpenState, ArrowLeft, ALT_CTRL),
          506     (
          507         scroll_area_right,
          508         "menu-scroll_area_right",
          509         ScrollAreaRight,
          510         BufferOpenState,
          511         ArrowRight,
          512         ALT_CTRL
          513     ),
          514     (set_reference_image, "menu-reference-image", SetReferenceImage, BufferOpenState, O, CTRL_SHIFT),
          515     (
          516         toggle_reference_image,
          517         "menu-toggle-reference-image",
          518         ToggleReferenceImage,
          519         BufferOpenState,
          520         Tab,
          521         CTRL
          522     ),
          523     (clear_reference_image, "menu-clear-reference-image", ClearReferenceImage, BufferOpenState),
          524     (
          525         pick_attribute_under_caret,
          526         "menu-pick_attribute_under_caret",
          527         PickAttributeUnderCaret,
          528         BufferOpenState,
          529         U,
          530         ALT
          531     ),
          532     (switch_to_default_color, "menu-default_color", SwitchToDefaultColor, BufferOpenState, D, CTRL),
          533     (toggle_color, "menu-toggle_color", ToggleColor, BufferOpenState, X, ALT),
          534     (fullscreen, "menu-toggle_fullscreen", ToggleFullScreen, AlwaysEnabledState, Enter, ALT),
          535     (zoom_reset, "menu-zoom_reset", ZoomReset, BufferOpenState, Backspace, CTRL),
          536     (zoom_in, "menu-zoom_in", ZoomIn, BufferOpenState, Plus, CTRL),
          537     (zoom_out, "menu-zoom_out", ZoomOut, BufferOpenState, Minus, CTRL),
          538     (open_tdf_directory, "menu-open_tdf_directoy", OpenTdfDirectory, AlwaysEnabledState),
          539     (open_font_selector, "menu-open_font_selector", OpenFontSelector, BufferOpenState),
          540     (add_fonts, "menu-add_fonts", OpenAddFonts, BufferOpenState),
          541     (open_font_manager, "menu-open_font_manager", OpenFontManager, BufferOpenState),
          542     (open_font_directory, "menu-open_font_directoy", OpenFontDirectory, AlwaysEnabledState),
          543     (
          544         open_palettes_directory,
          545         "menu-open_palettes_directoy",
          546         OpenPalettesDirectory,
          547         AlwaysEnabledState
          548     ),
          549     (mirror_mode, "menu-mirror_mode", ToggleMirrorMode, BufferOpenState),
          550     (clear_recent_open, "menu-open_recent_clear", ClearRecentOpenFiles, HasRecentFilesState),
          551     (inverse_selection, "menu-inverse_selection", InverseSelection, BufferOpenState),
          552     (clear_selection, "menu-delete_row", ClearSelection, BufferOpenState, Escape, NONE),
          553     (select_palette, "menu-select_palette", SelectPalette, CanSwitchPaletteState),
          554     (show_layer_borders, "menu-show_layer_borders", ToggleLayerBorders, LayerBordersState),
          555     (show_line_numbers, "menu-show_line_numbers", ToggleLineNumbers, LineNumberState),
          556     (open_plugin_directory, "menu-open_plugin_directory", OpenPluginDirectory, AlwaysEnabledState),
          557     (next_fg_color, "menu-next_fg_color", NextFgColor, BufferOpenState, ArrowDown, CTRL),
          558     (prev_fg_color, "menu-prev_fg_color", PreviousFgColor, BufferOpenState, ArrowUp, CTRL),
          559     (next_bg_color, "menu-next_bg_color", NextBgColor, BufferOpenState, ArrowRight, CTRL),
          560     (prev_bg_color, "menu-prev_bg_color", PreviousBgColor, BufferOpenState, ArrowLeft, CTRL),
          561     (lga_font, "menu-9px-font", ToggleLGAFont, LGAFontState),
          562     (aspect_ratio, "menu-aspect-ratio", ToggleAspectRatio, AspectRatioState),
          563     (toggle_grid_guides, "menu-toggle_grid", ToggleGrid, BufferOpenState),
          564 ];