Improved the command system. - 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
       ---
 (DIR) commit a8913bffc7d423d83e48965a587e1d5fd624cf96
 (DIR) parent e37433fae9588e4ad18b7182a55ffbc7ac1ae3d4
 (HTM) Author: Mike Krüger <mkrueger@posteo.de>
       Date:   Mon, 18 Sep 2023 11:22:19 +0200
       
       Improved the command system.
       
       Diffstat:
         M i18n/de/icy_draw.ftl                |       1 +
         M i18n/en/icy_draw.ftl                |       1 +
         M src/ui/commands.rs                  |     458 ++++++++++++++++++++++++++-----
         M src/ui/dialogs/save_file_dialog.rs  |       2 +-
         M src/ui/dialogs/select_palette_dial… |       7 +++++++
         M src/ui/editor/ansi/mod.rs           |      37 +++++++++----------------------
         M src/ui/main_window.rs               |      58 +++++++++++++++++++++++++------
         M src/ui/messages.rs                  |       4 ++--
         M src/ui/top_bar.rs                   |     270 +++++++++++++++++++++----------
       
       9 files changed, 646 insertions(+), 192 deletions(-)
       ---
 (DIR) diff --git a/i18n/de/icy_draw.ftl b/i18n/de/icy_draw.ftl
       @@ -343,6 +343,7 @@ new-file-template-thedraw-ui-label=
            Eine große Font-Sammlung kann hier heruntergeladen werden:
        
        palette_selector-dos_default_palette=VGA 16 Farben
       +palette_selector-dos_default_low_palette=VGA 8 Farben
        palette_selector-c64_default_palette=C64 Farben
        palette_selector-ega_default_palette=EGA 64 Farben
        palette_selector-xterm_default_palette=XTerm erweiterte Palette
 (DIR) diff --git a/i18n/en/icy_draw.ftl b/i18n/en/icy_draw.ftl
       @@ -346,6 +346,7 @@ new-file-template-thedraw-ui-label=
        manage-font-dialog-title=Manage Fonts
        
        palette_selector-dos_default_palette=VGA 16 colors
       +palette_selector-dos_default_low_palette=VGA 8 colors
        palette_selector-c64_default_palette=C64 colors
        palette_selector-ega_default_palette=EGA 64 colors
        palette_selector-xterm_default_palette=XTerm extended colors
 (DIR) diff --git a/src/ui/commands.rs b/src/ui/commands.rs
       @@ -2,26 +2,168 @@ use eframe::egui::{self, Modifiers};
        use egui_bind::{BindTarget, KeyOrPointer};
        use i18n_embed_fl::fl;
        
       -use crate::{button_with_shortcut, MainWindow, Message};
       +use crate::{button_with_shortcut, DocumentTab, Message, SETTINGS};
        
       -trait Command {
       -    fn is_pressed(&self, ctx: &egui::Context) -> bool;
       -    fn is_enabled(&self, _window: &MainWindow) -> bool {
       +pub trait CommandState {
       +    fn is_enabled(&self, _open_tab_opt: Option<&DocumentTab>) -> bool {
                true
            }
       -    fn is_checked(&self, _window: &MainWindow) -> Option<bool> {
       +    fn is_checked(&self, _open_tab_opt: Option<&DocumentTab>) -> Option<bool> {
                None
            }
       -    fn run(&self, _window: &MainWindow) -> Option<Message> {
       -        None
       +}
       +
       +#[derive(Default)]
       +pub struct AlwaysEnabledState {}
       +impl CommandState for AlwaysEnabledState {}
       +
       +#[derive(Default)]
       +pub struct BufferOpenState {}
       +
       +impl CommandState for BufferOpenState {
       +    fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
       +        if let Some(pane) = open_tab_opt {
       +            if let Ok(doc) = pane.doc.lock() {
       +                return doc.get_ansi_editor().is_some();
       +            }
       +        }
       +        false
       +    }
       +}
       +
       +#[derive(Default)]
       +pub struct LayerBordersState {}
       +
       +impl CommandState for LayerBordersState {
       +    fn is_enabled(&self, _open_tab_opt: Option<&DocumentTab>) -> bool {
       +        true
       +    }
       +
       +    fn is_checked(&self, _open_tab_opt: Option<&DocumentTab>) -> Option<bool> {
       +        unsafe { Some(SETTINGS.show_layer_borders) }
            }
       -    fn ui(&self, ui: &mut egui::Ui, message: &mut Option<Message>);
        }
        
       -pub struct BasicCommand {
       +#[derive(Default)]
       +pub struct LineNumberState {}
       +
       +impl CommandState for LineNumberState {
       +    fn is_enabled(&self, _open_tab_opt: Option<&DocumentTab>) -> bool {
       +        true
       +    }
       +
       +    fn is_checked(&self, _open_tab_opt: Option<&DocumentTab>) -> Option<bool> {
       +        unsafe { Some(SETTINGS.show_line_numbers) }
       +    }
       +}
       +
       +#[derive(Default)]
       +pub struct FileOpenState {}
       +
       +impl CommandState for FileOpenState {
       +    fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
       +        open_tab_opt.is_some()
       +    }
       +}
       +
       +#[derive(Default)]
       +pub struct FileIsDirtyState {}
       +
       +impl CommandState for FileIsDirtyState {
       +    fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
       +        if let Some(pane) = open_tab_opt {
       +            pane.is_dirty()
       +        } else {
       +            false
       +        }
       +    }
       +}
       +
       +#[derive(Default)]
       +pub struct HasRecentFilesState {}
       +
       +impl CommandState for HasRecentFilesState {
       +    fn is_enabled(&self, _open_tab_opt: Option<&DocumentTab>) -> bool {
       +        unsafe { !SETTINGS.get_recent_files().is_empty() }
       +    }
       +}
       +
       +#[derive(Default)]
       +pub struct CanUndoState {}
       +
       +impl CommandState for CanUndoState {
       +    fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
       +        if let Some(pane) = open_tab_opt {
       +            if let Ok(doc) = pane.doc.lock() {
       +                return doc.can_undo();
       +            }
       +        }
       +        false
       +    }
       +}
       +#[derive(Default)]
       +pub struct CanRedoState {}
       +
       +impl CommandState for CanRedoState {
       +    fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
       +        if let Some(pane) = open_tab_opt {
       +            if let Ok(doc) = pane.doc.lock() {
       +                return doc.can_redo();
       +            }
       +        }
       +        false
       +    }
       +}
       +
       +#[derive(Default)]
       +pub struct CanCutState {}
       +
       +impl CommandState for CanCutState {
       +    fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
       +        if let Some(pane) = open_tab_opt {
       +            if let Ok(doc) = pane.doc.lock() {
       +                return doc.can_cut();
       +            }
       +        }
       +        false
       +    }
       +}
       +
       +#[derive(Default)]
       +pub struct CanCopyState {}
       +
       +impl CommandState for CanCopyState {
       +    fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
       +        if let Some(pane) = open_tab_opt {
       +            if let Ok(doc) = pane.doc.lock() {
       +                return doc.can_copy();
       +            }
       +        }
       +        false
       +    }
       +}
       +
       +#[derive(Default)]
       +pub struct CanPasteState {}
       +
       +impl CommandState for CanPasteState {
       +    fn is_enabled(&self, open_tab_opt: Option<&DocumentTab>) -> bool {
       +        if let Some(pane) = open_tab_opt {
       +            if let Ok(doc) = pane.doc.lock() {
       +                return doc.can_paste();
       +            }
       +        }
       +        false
       +    }
       +}
       +
       +pub struct CommandWrapper {
            key: Option<(KeyOrPointer, Modifiers)>,
            message: Message,
       -    description: String,
       +    label: String,
       +    is_enabled: bool,
       +    is_checked: Option<bool>,
       +    state: Box<dyn CommandState>,
        }
        
        mod modifier_keys {
       @@ -78,10 +220,10 @@ macro_rules! key {
        }
        
        macro_rules! keys {
       -    ($( ($l:ident, $translation: expr, $message:ident$(, $key:ident, $modifier: ident)? ) ),* $(,)? ) => {
       +    ($( ($l:ident, $translation: expr, $message:ident, $cmd_state: ident$(, $key:ident, $modifier: ident)? ) ),* $(,)? ) => {
                pub struct Commands {
                    $(
       -                pub $l: BasicCommand,
       +                pub $l: CommandWrapper,
                    )*
                }
        
       @@ -89,7 +231,7 @@ macro_rules! keys {
                    fn default() -> Self {
                        Self {
                            $(
       -                        $l: BasicCommand::new(key!($($key, $modifier)?), Message::$message, fl!(crate::LANGUAGE_LOADER, $translation)),
       +                        $l: CommandWrapper::new(key!($($key, $modifier)?), Message::$message, fl!(crate::LANGUAGE_LOADER, $translation), Box::<$cmd_state>::default()),
                            )*
                        }
                    }
       @@ -104,31 +246,57 @@ macro_rules! keys {
                            }
                        )*
                    }
       +
       +            pub fn update_states(&mut self, open_tab_opt: Option<&DocumentTab>) {
       +                $(
       +                    self.$l.update_state(open_tab_opt);
       +                )*
       +            }
       +
                }
            };
        }
        
       -impl BasicCommand {
       +impl CommandWrapper {
            pub fn new(
                key: Option<(KeyOrPointer, Modifiers)>,
                message: Message,
                description: String,
       +        state: Box<dyn CommandState>,
            ) -> Self {
                Self {
                    key,
                    message,
       -            description,
       +            label: description,
       +            state,
       +            is_enabled: true,
       +            is_checked: None,
                }
            }
        
       +    pub fn update_state(&mut self, open_tab: Option<&DocumentTab>) {
       +        self.is_enabled = self.state.is_enabled(open_tab);
       +        self.is_checked = self.state.is_checked(open_tab);
       +    }
       +
            pub fn is_pressed(&self, ctx: &egui::Context) -> bool {
                self.key.pressed(ctx)
            }
        
       -    pub fn ui_enabled(&self, ui: &mut egui::Ui, enabled: bool, message: &mut Option<Message>) {
       -        let response = ui.with_layout(ui.layout().with_cross_justify(true), |ui| {
       -            ui.set_enabled(enabled);
       +    pub fn ui(&self, ui: &mut egui::Ui, message: &mut Option<Message>) {
       +        if let Some(mut checked) = self.is_checked {
       +            if ui
       +                .add(egui::Checkbox::new(&mut checked, &self.label))
       +                .clicked()
       +            {
       +                *message = Some(self.message.clone());
       +                ui.close_menu();
       +            }
       +            return;
       +        }
        
       +        let response = ui.with_layout(ui.layout().with_cross_justify(true), |ui| {
       +            ui.set_enabled(self.is_enabled);
                    if let Some((KeyOrPointer::Key(k), modifier)) = self.key {
                        let mut shortcut = k.name().to_string();
        
       @@ -144,9 +312,9 @@ impl BasicCommand {
                            shortcut.insert_str(0, "Shift+");
                        }
        
       -                button_with_shortcut(ui, true, &self.description, shortcut)
       +                button_with_shortcut(ui, true, &self.label, shortcut)
                    } else {
       -                ui.add(egui::Button::new(&self.description).wrap(false))
       +                ui.add(egui::Button::new(&self.label).wrap(false))
                    }
                });
        
       @@ -155,43 +323,98 @@ impl BasicCommand {
                    ui.close_menu();
                }
            }
       -
       -    pub fn ui(&self, ui: &mut egui::Ui, message: &mut Option<Message>) {
       -        self.ui_enabled(ui, true, message)
       -    }
        }
        
        keys![
       -    (new_file, "menu-new", NewFileDialog, N, CTRL),
       -    (save, "menu-save", SaveFile, S, CTRL),
       -    (save_as, "menu-save-as", SaveFileAs, S, CTRL_SHIFT),
       -    (open_file, "menu-open", OpenFileDialog, O, CTRL),
       -    (export, "menu-export", ExportFile),
       +    (
       +        new_file,
       +        "menu-new",
       +        NewFileDialog,
       +        AlwaysEnabledState,
       +        N,
       +        CTRL
       +    ),
       +    (save, "menu-save", SaveFile, FileIsDirtyState, S, CTRL),
       +    (
       +        save_as,
       +        "menu-save-as",
       +        SaveFileAs,
       +        FileOpenState,
       +        S,
       +        CTRL_SHIFT
       +    ),
       +    (
       +        open_file,
       +        "menu-open",
       +        OpenFileDialog,
       +        AlwaysEnabledState,
       +        O,
       +        CTRL
       +    ),
       +    (export, "menu-export", ExportFile, BufferOpenState),
            (
                edit_font_outline,
                "menu-edit-font-outline",
       -        ShowOutlineDialog
       -    ),
       -    (close_window, "menu-close", CloseWindow, Q, CTRL),
       -    (undo, "menu-undo", Undo, Z, CTRL),
       -    (redo, "menu-redo", Redo, Z, CTRL_SHIFT),
       -    (cut, "menu-cut", Cut, X, CTRL),
       -    (copy, "menu-copy", Copy, C, CTRL),
       -    (paste, "menu-paste", Paste, V, CTRL),
       -    (select_all, "menu-select-all", SelectAll, A, CTRL),
       -    (deselect, "menu-select_nothing", SelectNothing),
       -    (erase_selection, "menu-erase", DeleteSelection, Delete, NONE),
       -    (flip_x, "menu-flipx", FlipX),
       -    (flip_y, "menu-flipy", FlipY),
       -    (justifycenter, "menu-justifycenter", Center),
       -    (justifyleft, "menu-justifyleft", JustifyLeft),
       -    (justifyright, "menu-justifyright", JustifyRight),
       -    (crop, "menu-crop", Crop),
       -    (about, "menu-about", ShowAboutDialog),
       +        ShowOutlineDialog,
       +        AlwaysEnabledState
       +    ),
       +    (
       +        close_window,
       +        "menu-close",
       +        CloseWindow,
       +        BufferOpenState,
       +        Q,
       +        CTRL
       +    ),
       +    (undo, "menu-undo", Undo, CanUndoState, Z, CTRL),
       +    (redo, "menu-redo", Redo, CanRedoState, Z, CTRL_SHIFT),
       +    (cut, "menu-cut", Cut, CanCutState, X, CTRL),
       +    (copy, "menu-copy", Copy, CanCopyState, C, CTRL),
       +    (paste, "menu-paste", Paste, CanPasteState, V, CTRL),
       +    (
       +        select_all,
       +        "menu-select-all",
       +        SelectAll,
       +        BufferOpenState,
       +        A,
       +        CTRL
       +    ),
       +    (
       +        deselect,
       +        "menu-select_nothing",
       +        SelectNothing,
       +        BufferOpenState
       +    ),
       +    (
       +        erase_selection,
       +        "menu-erase",
       +        DeleteSelection,
       +        BufferOpenState,
       +        Delete,
       +        NONE
       +    ),
       +    (flip_x, "menu-flipx", FlipX, BufferOpenState),
       +    (flip_y, "menu-flipy", FlipY, BufferOpenState),
       +    (justifycenter, "menu-justifycenter", Center, BufferOpenState),
       +    (
       +        justifyleft,
       +        "menu-justifyleft",
       +        JustifyLeft,
       +        BufferOpenState
       +    ),
       +    (
       +        justifyright,
       +        "menu-justifyright",
       +        JustifyRight,
       +        BufferOpenState
       +    ),
       +    (crop, "menu-crop", Crop, BufferOpenState),
       +    (about, "menu-about", ShowAboutDialog, AlwaysEnabledState),
            (
                justify_line_center,
                "menu-justify_line_center",
                CenterLine,
       +        BufferOpenState,
                C,
                ALT
            ),
       @@ -199,6 +422,7 @@ keys![
                justify_line_left,
                "menu-justify_line_left",
                JustifyLineLeft,
       +        BufferOpenState,
                L,
                ALT
            ),
       @@ -206,15 +430,31 @@ keys![
                justify_line_right,
                "menu-justify_line_right",
                JustifyLineRight,
       +        BufferOpenState,
                R,
                ALT
            ),
       -    (insert_row, "menu-insert_row", InsertRow, ArrowUp, CTRL),
       -    (delete_row, "menu-delete_row", DeleteRow, ArrowDown, CTRL),
       +    (
       +        insert_row,
       +        "menu-insert_row",
       +        InsertRow,
       +        BufferOpenState,
       +        ArrowUp,
       +        CTRL
       +    ),
       +    (
       +        delete_row,
       +        "menu-delete_row",
       +        DeleteRow,
       +        BufferOpenState,
       +        ArrowDown,
       +        CTRL
       +    ),
            (
                insert_column,
                "menu-insert_colum",
                InsertColumn,
       +        BufferOpenState,
                ArrowRight,
                CTRL
            ),
       @@ -222,14 +462,23 @@ keys![
                delete_column,
                "menu-delete_colum",
                DeleteColumn,
       +        BufferOpenState,
                ArrowLeft,
                CTRL
            ),
       -    (erase_row, "menu-erase_row", EraseRow, E, ALT),
       +    (
       +        erase_row,
       +        "menu-erase_row",
       +        EraseRow,
       +        BufferOpenState,
       +        E,
       +        ALT
       +    ),
            (
                erase_row_to_start,
                "menu-erase_row_to_start",
                EraseRowToStart,
       +        BufferOpenState,
                Home,
                ALT
            ),
       @@ -237,14 +486,23 @@ keys![
                erase_row_to_end,
                "menu-erase_row_to_end",
                EraseRowToEnd,
       +        BufferOpenState,
                End,
                ALT
            ),
       -    (erase_column, "menu-erase_column", EraseColumn, E, ALT),
       +    (
       +        erase_column,
       +        "menu-erase_column",
       +        EraseColumn,
       +        BufferOpenState,
       +        E,
       +        ALT
       +    ),
            (
                erase_column_to_start,
                "menu-erase_column_to_start",
                EraseColumnToStart,
       +        BufferOpenState,
                Home,
                ALT
            ),
       @@ -252,6 +510,7 @@ keys![
                erase_column_to_end,
                "menu-erase_column_to_end",
                EraseColumnToEnd,
       +        BufferOpenState,
                End,
                ALT
            ),
       @@ -259,6 +518,7 @@ keys![
                scroll_area_up,
                "menu-scroll_area_up",
                ScrollAreaUp,
       +        BufferOpenState,
                ArrowUp,
                ALT_CTRL
            ),
       @@ -266,6 +526,7 @@ keys![
                scroll_area_down,
                "menu-scroll_area_down",
                ScrollAreaDown,
       +        BufferOpenState,
                ArrowDown,
                ALT_CTRL
            ),
       @@ -273,6 +534,7 @@ keys![
                scroll_area_left,
                "menu-scroll_area_left",
                ScrollAreaLeft,
       +        BufferOpenState,
                ArrowLeft,
                ALT_CTRL
            ),
       @@ -280,6 +542,7 @@ keys![
                scroll_area_right,
                "menu-scroll_area_right",
                ScrollAreaRight,
       +        BufferOpenState,
                ArrowRight,
                ALT_CTRL
            ),
       @@ -287,6 +550,7 @@ keys![
                set_reference_image,
                "menu-reference-image",
                SetReferenceImage,
       +        BufferOpenState,
                O,
                CTRL_SHIFT
            ),
       @@ -294,18 +558,21 @@ keys![
                toggle_reference_image,
                "menu-toggle-reference-image",
                ToggleReferenceImage,
       +        BufferOpenState,
                Tab,
                CTRL
            ),
            (
                clear_reference_image,
                "menu-clear-reference-image",
       -        ClearReferenceImage
       +        ClearReferenceImage,
       +        BufferOpenState
            ),
            (
                pick_attribute_under_caret,
                "menu-pick_attribute_under_caret",
                PickAttributeUnderCaret,
       +        BufferOpenState,
                U,
                ALT
            ),
       @@ -313,73 +580,128 @@ keys![
                switch_to_default_color,
                "menu-default_color",
                SwitchToDefaultColor,
       +        BufferOpenState,
                D,
                CTRL
            ),
       -    (toggle_color, "menu-toggle_color", ToggleColor, X, ALT),
       +    (
       +        toggle_color,
       +        "menu-toggle_color",
       +        ToggleColor,
       +        BufferOpenState,
       +        X,
       +        ALT
       +    ),
            (
                fullscreen,
                "menu-toggle_fullscreen",
                ToggleFullScreen,
       +        AlwaysEnabledState,
                F11,
                NONE
            ),
       -    (zoom_reset, "menu-zoom_reset", ZoomReset, Num0, CTRL),
       -    (zoom_in, "menu-zoom_in", ZoomIn, PlusEquals, CTRL),
       -    (zoom_out, "menu-zoom_out", ZoomOut, Minus, CTRL),
       +    (
       +        zoom_reset,
       +        "menu-zoom_reset",
       +        ZoomReset,
       +        BufferOpenState,
       +        Num0,
       +        CTRL
       +    ),
       +    (
       +        zoom_in,
       +        "menu-zoom_in",
       +        ZoomIn,
       +        BufferOpenState,
       +        PlusEquals,
       +        CTRL
       +    ),
       +    (
       +        zoom_out,
       +        "menu-zoom_out",
       +        ZoomOut,
       +        BufferOpenState,
       +        Minus,
       +        CTRL
       +    ),
            (
                open_tdf_directory,
                "menu-open_tdf_directoy",
       -        OpenTdfDirectory
       +        OpenTdfDirectory,
       +        AlwaysEnabledState
            ),
            (
                open_font_selector,
                "menu-open_font_selector",
       -        OpenFontSelector
       +        OpenFontSelector,
       +        BufferOpenState
       +    ),
       +    (
       +        open_font_manager,
       +        "menu-open_font_manager",
       +        OpenFontManager,
       +        BufferOpenState
            ),
       -    (open_font_manager, "menu-open_font_manager", OpenFontManager),
            (
                open_font_directory,
                "menu-open_font_directoy",
       -        OpenFontDirectory
       +        OpenFontDirectory,
       +        AlwaysEnabledState
            ),
            (
                open_palettes_directory,
                "menu-open_palettes_directoy",
       -        OpenPalettesDirectory
       +        OpenPalettesDirectory,
       +        AlwaysEnabledState
       +    ),
       +    (
       +        mirror_mode,
       +        "menu-mirror_mode",
       +        ToggleMirrorMode,
       +        BufferOpenState
            ),
       -    (mirror_mode, "menu-mirror_mode", ToggleMirrorMode),
            (
                clear_recent_open,
                "menu-open_recent_clear",
       -        ClearRecentOpenFiles
       +        ClearRecentOpenFiles,
       +        HasRecentFilesState
            ),
            (
                inverse_selection,
                "menu-inverse_selection",
       -        InverseSelection
       +        InverseSelection,
       +        BufferOpenState
            ),
            (
                clear_selection,
                "menu-delete_row",
                ClearSelection,
       +        BufferOpenState,
                Escape,
                NONE
            ),
       -    (select_palette, "menu-select_palette", SelectPalette),
       +    (
       +        select_palette,
       +        "menu-select_palette",
       +        SelectPalette,
       +        BufferOpenState
       +    ),
            (
                show_layer_borders,
                "menu-show_layer_borders",
       -        ToggleLayerBorders
       +        ToggleLayerBorders,
       +        LayerBordersState
            ),
            (
                show_line_numbers,
                "menu-show_line_numbers",
       -        ToggleLineNumbers
       +        ToggleLineNumbers,
       +        LineNumberState
            ),
            (
                open_plugin_directory,
                "menu-open_plugin_directory",
       -        OpenPluginDirectory
       +        OpenPluginDirectory,
       +        AlwaysEnabledState
            ),
        ];
 (DIR) diff --git a/src/ui/dialogs/save_file_dialog.rs b/src/ui/dialogs/save_file_dialog.rs
       @@ -44,7 +44,7 @@ impl crate::ModalDialog for SaveFileDialog {
        
            fn commit_self(&self, window: &mut MainWindow) -> crate::TerminalResult<Option<Message>> {
                if let Some(file) = &self.opened_file.clone() {
       -            if let Some(pane) = window.get_active_pane() {
       +            if let Some(pane) = window.get_active_pane_mut() {
                        pane.set_path(file.clone());
                        pane.save();
                    }
 (DIR) diff --git a/src/ui/dialogs/select_palette_dialog.rs b/src/ui/dialogs/select_palette_dialog.rs
       @@ -47,6 +47,13 @@ impl SelectPaletteDialog {
                );
                palettes.push((dos, PaletteSource::BuiltIn));
        
       +        let mut dos = Palette::from_colors(DOS_DEFAULT_PALETTE[0..8].to_vec());
       +        dos.title = fl!(
       +            crate::LANGUAGE_LOADER,
       +            "palette_selector-dos_default_low_palette"
       +        );
       +        palettes.push((dos, PaletteSource::BuiltIn));
       +
                let mut dos = Palette::from_colors(C64_DEFAULT_PALETTE.to_vec());
                dos.title = fl!(
                    crate::LANGUAGE_LOADER,
 (DIR) diff --git a/src/ui/editor/ansi/mod.rs b/src/ui/editor/ansi/mod.rs
       @@ -749,37 +749,20 @@ pub fn terminal_context_menu(
            let mut result = None;
            ui.input_mut(|i| i.events.clear());
        
       -    if ui.button(fl!(crate::LANGUAGE_LOADER, "menu-cut")).clicked() {
       -        result = Some(Message::Cut);
       -        ui.close_menu();
       -    }
       -
       -    if ui
       -        .button(fl!(crate::LANGUAGE_LOADER, "menu-copy"))
       -        .clicked()
       -    {
       -        result = Some(Message::Copy);
       -        ui.close_menu();
       -    }
       -
       -    if ui
       -        .button(fl!(crate::LANGUAGE_LOADER, "menu-paste"))
       -        .clicked()
       -    {
       -        result = Some(Message::Paste);
       -        ui.close_menu();
       -    }
       +    commands.cut.ui(ui, &mut result);
       +    commands.copy.ui(ui, &mut result);
       +    commands.paste.ui(ui, &mut result);
        
            let sel = editor.buffer_view.lock().get_selection();
        
            if let Some(_sel) = sel {
       -        commands.erase_selection.ui_enabled(ui, true, &mut result);
       -        commands.flip_x.ui_enabled(ui, true, &mut result);
       -        commands.flip_y.ui_enabled(ui, true, &mut result);
       -        commands.justifycenter.ui_enabled(ui, true, &mut result);
       -        commands.justifyleft.ui_enabled(ui, true, &mut result);
       -        commands.justifyright.ui_enabled(ui, true, &mut result);
       -        commands.crop.ui_enabled(ui, true, &mut result);
       +        commands.erase_selection.ui(ui, &mut result);
       +        commands.flip_x.ui(ui, &mut result);
       +        commands.flip_y.ui(ui, &mut result);
       +        commands.justifycenter.ui(ui, &mut result);
       +        commands.justifyleft.ui(ui, &mut result);
       +        commands.justifyright.ui(ui, &mut result);
       +        commands.crop.ui(ui, &mut result);
            }
            result
        }
 (DIR) diff --git a/src/ui/main_window.rs b/src/ui/main_window.rs
       @@ -14,11 +14,10 @@ use crate::{
            ToolTab, TopBar,
        };
        use directories::UserDirs;
       -use eframe::egui::accesskit::Role::MenuItem;
       -use eframe::egui::{Button, Sense};
       +use eframe::egui::Button;
        use eframe::{
       -    egui::{self, Key, Response, SidePanel, TextStyle, Ui},
       -    epaint::{pos2, FontId},
       +    egui::{self, Key, Response, SidePanel, Ui},
       +    epaint::FontId,
        };
        use egui_tiles::{Container, TileId};
        use glow::Context;
       @@ -43,7 +42,7 @@ pub struct MainWindow {
            pub right_panel: bool,
            pub bottom_panel: bool,
        
       -    pub commands: Commands,
       +    pub commands: Option<Commands>,
            pub is_fullscreen: bool,
        
            pub in_open_file_mode: bool,
       @@ -203,7 +202,7 @@ impl MainWindow {
                    right_panel: true,
                    bottom_panel: false,
                    top_bar: TopBar::new(&cc.egui_ctx),
       -            commands: Commands::default(),
       +            commands: Some(Commands::default()),
                    is_closed: false,
                    is_fullscreen: false,
                    in_open_file_mode: false,
       @@ -318,7 +317,46 @@ impl MainWindow {
                }
            }
        
       -    pub fn get_active_pane(&mut self) -> Option<&mut DocumentTab> {
       +    pub fn get_active_pane_mut(&mut self) -> Option<&mut DocumentTab> {
       +        let mut stack = vec![];
       +
       +        if let Some(root) = self.document_tree.root {
       +            stack.push(root);
       +        }
       +        while let Some(id) = stack.pop() {
       +            match self.document_tree.tiles.get(id) {
       +                Some(egui_tiles::Tile::Pane(_)) => {
       +                    if let Some(egui_tiles::Tile::Pane(p)) = self.document_tree.tiles.get_mut(id) {
       +                        return Some(p);
       +                    } else {
       +                        return None;
       +                    }
       +                }
       +                Some(egui_tiles::Tile::Container(container)) => match container {
       +                    egui_tiles::Container::Tabs(tabs) => {
       +                        if let Some(active) = tabs.active {
       +                            stack.push(active);
       +                        }
       +                    }
       +                    egui_tiles::Container::Linear(l) => {
       +                        for child in l.children.iter() {
       +                            stack.push(*child);
       +                        }
       +                    }
       +                    egui_tiles::Container::Grid(g) => {
       +                        for child in g.children() {
       +                            stack.push(*child);
       +                        }
       +                    }
       +                },
       +                None => {}
       +            }
       +        }
       +
       +        None
       +    }
       +
       +    pub fn get_active_pane(&mut self) -> Option<&DocumentTab> {
                let mut stack = vec![];
        
                if let Some(root) = self.document_tree.root {
       @@ -432,7 +470,7 @@ impl MainWindow {
            }
        
            pub fn get_active_document(&mut self) -> Option<Arc<Mutex<Box<dyn Document>>>> {
       -        if let Some(pane) = self.get_active_pane() {
       +        if let Some(pane) = self.get_active_pane_mut() {
                    return Some(pane.doc.clone());
                }
                None
       @@ -499,7 +537,7 @@ pub fn button_with_shortcut(
            shortcut: impl Into<String>,
        ) -> Response {
            let title = label.into();
       -    let mut button = Button::new(title).shortcut_text(shortcut.into());
       +    let button = Button::new(title).shortcut_text(shortcut.into());
            ui.add_enabled(enabled, button)
        }
        
       @@ -754,7 +792,7 @@ impl eframe::App for MainWindow {
                }
        
                let mut msg = self.document_behavior.message.take();
       -        self.commands.check(ctx, &mut msg);
       +        self.commands.as_ref().unwrap().check(ctx, &mut msg);
                self.handle_message(msg);
                self.handle_message(read_outline_keys(ctx));
        
 (DIR) diff --git a/src/ui/messages.rs b/src/ui/messages.rs
       @@ -160,7 +160,7 @@ impl MainWindow {
                        self.open_dialog(NewFileDialog::default());
                    }
                    Message::OpenFileDialog => {
       -                let mut initial_directory = if let Some(d) = self.get_active_pane() {
       +                let mut initial_directory = if let Some(d) = self.get_active_pane_mut() {
                            d.get_path()
                        } else {
                            None
       @@ -190,7 +190,7 @@ impl MainWindow {
                    }
        
                    Message::SaveFile => {
       -                let msg = if let Some(pane) = self.get_active_pane() {
       +                let msg = if let Some(pane) = self.get_active_pane_mut() {
                            if pane.is_untitled() {
                                Some(Message::SaveFileAs)
                            } else {
 (DIR) diff --git a/src/ui/top_bar.rs b/src/ui/top_bar.rs
       @@ -35,6 +35,7 @@ impl MainWindow {
                frame: &mut eframe::Frame,
            ) -> Option<Message> {
                let mut result = None;
       +
                TopBottomPanel::top("top_panel")
                    .exact_height(24.0)
                    .show(ctx, |ui| {
       @@ -45,13 +46,16 @@ impl MainWindow {
        
            fn main_menu(&mut self, ui: &mut Ui, frame: &mut eframe::Frame) -> Option<Message> {
                let mut result = None;
       +
                menu::bar(ui, |ui| {
                    let mut has_buffer = false;
       -            let mut is_dirty = false;
       -            let mut has_pane = false;
       -            if let Some(pane) = self.get_active_pane() {
       -                is_dirty = pane.is_dirty();
       -                has_pane = true;
       +            if let Some(mut cmd) = self.commands.take() {
       +                let pane = self.get_active_pane();
       +                cmd.update_states(pane);
       +                self.commands = Some(cmd);
       +            }
       +
       +            if let Some(pane) = self.get_active_pane_mut() {
                        if let Ok(doc) = pane.doc.lock() {
                            has_buffer = doc.get_ansi_editor().is_some();
                        }
       @@ -60,8 +64,12 @@ impl MainWindow {
                    ui.menu_button(fl!(crate::LANGUAGE_LOADER, "menu-file"), |ui| {
                        ui.set_min_width(300.0);
        
       -                self.commands.new_file.ui(ui, &mut result);
       -                self.commands.open_file.ui(ui, &mut result);
       +                self.commands.as_ref().unwrap().new_file.ui(ui, &mut result);
       +                self.commands
       +                    .as_ref()
       +                    .unwrap()
       +                    .open_file
       +                    .ui(ui, &mut result);
                        ui.menu_button(
                            fl!(crate::LANGUAGE_LOADER, "menu-open_recent"),
                            |ui| unsafe {
       @@ -78,21 +86,29 @@ impl MainWindow {
                                    }
                                    ui.separator();
                                }
       -                        self.commands.clear_recent_open.ui_enabled(
       -                            ui,
       -                            !get_recent_files.is_empty(),
       -                            &mut result,
       -                        );
       +                        self.commands
       +                            .as_ref()
       +                            .unwrap()
       +                            .clear_recent_open
       +                            .ui(ui, &mut result);
                            },
                        );
                        ui.separator();
       -                self.commands.save.ui_enabled(ui, is_dirty, &mut result);
       -                self.commands.save_as.ui_enabled(ui, has_pane, &mut result);
       -                self.commands.export.ui_enabled(ui, has_buffer, &mut result);
       +                self.commands.as_ref().unwrap().save.ui(ui, &mut result);
       +                self.commands.as_ref().unwrap().save_as.ui(ui, &mut result);
       +                self.commands.as_ref().unwrap().export.ui(ui, &mut result);
                        ui.separator();
       -                self.commands.edit_font_outline.ui(ui, &mut result);
       +                self.commands
       +                    .as_ref()
       +                    .unwrap()
       +                    .edit_font_outline
       +                    .ui(ui, &mut result);
                        ui.separator();
       -                self.commands.close_window.ui(ui, &mut result);
       +                self.commands
       +                    .as_ref()
       +                    .unwrap()
       +                    .close_window
       +                    .ui(ui, &mut result);
                    });
        
                    ui.menu_button(fl!(crate::LANGUAGE_LOADER, "menu-edit"), |ui| {
       @@ -117,7 +133,7 @@ impl MainWindow {
                                    ui.close_menu();
                                }
                            } else {
       -                        self.commands.undo.ui_enabled(ui, false, &mut result);
       +                        self.commands.as_ref().unwrap().undo.ui(ui, &mut result);
                            }
        
                            if doc.lock().unwrap().can_redo() {
       @@ -136,25 +152,17 @@ impl MainWindow {
                                    ui.close_menu();
                                }
                            } else {
       -                        self.commands.redo.ui_enabled(ui, false, &mut result);
       +                        self.commands.as_ref().unwrap().redo.ui(ui, &mut result);
                            }
                        } else {
       -                    self.commands.undo.ui_enabled(ui, false, &mut result);
       -                    self.commands.redo.ui_enabled(ui, false, &mut result);
       +                    self.commands.as_ref().unwrap().undo.ui(ui, &mut result);
       +                    self.commands.as_ref().unwrap().redo.ui(ui, &mut result);
                        }
                        ui.separator();
       -                if let Some(doc) = self.get_active_document() {
       -                    self.commands
       -                        .cut
       -                        .ui_enabled(ui, doc.lock().unwrap().can_cut(), &mut result);
       -                    self.commands
       -                        .copy
       -                        .ui_enabled(ui, doc.lock().unwrap().can_copy(), &mut result);
       -                    self.commands.paste.ui_enabled(
       -                        ui,
       -                        doc.lock().unwrap().can_paste(),
       -                        &mut result,
       -                    );
       +                if self.get_active_document().is_some() {
       +                    self.commands.as_ref().unwrap().cut.ui(ui, &mut result);
       +                    self.commands.as_ref().unwrap().copy.ui(ui, &mut result);
       +                    self.commands.as_ref().unwrap().paste.ui(ui, &mut result);
                        }
        
                        ui.menu_button(fl!(crate::LANGUAGE_LOADER, "menu-paste-as"), |ui| {
       @@ -189,65 +197,101 @@ impl MainWindow {
                            ui.set_min_width(300.0);
        
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .justify_line_left
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .justify_line_right
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .justify_line_center
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            ui.separator();
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .insert_row
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .delete_row
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            ui.separator();
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .insert_column
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .delete_column
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            ui.separator();
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .erase_row
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .erase_row_to_start
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .erase_row_to_end
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            ui.separator();
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .erase_column
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .erase_column_to_end
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .erase_column_to_start
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            ui.separator();
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .scroll_area_up
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .scroll_area_down
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .scroll_area_left
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                            self.commands
       +                        .as_ref()
       +                        .unwrap()
                                .scroll_area_right
       -                        .ui_enabled(ui, has_buffer, &mut result);
       +                        .ui(ui, &mut result);
                        });
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .mirror_mode
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
        
                        ui.separator();
                        if ui
       @@ -279,36 +323,46 @@ impl MainWindow {
                        ui.style_mut().wrap = Some(false);
                        ui.set_min_width(200.0);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .select_all
       -                    .ui_enabled(ui, has_buffer, &mut result);
       -                self.commands
       -                    .deselect
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
       +                self.commands.as_ref().unwrap().deselect.ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .inverse_selection
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        ui.separator();
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .erase_selection
       -                    .ui_enabled(ui, has_buffer, &mut result);
       -                self.commands.flip_x.ui_enabled(ui, has_buffer, &mut result);
       -                self.commands.flip_y.ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
       +                self.commands.as_ref().unwrap().flip_x.ui(ui, &mut result);
       +                self.commands.as_ref().unwrap().flip_y.ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .justifycenter
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .justifyleft
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .justifyright
       -                    .ui_enabled(ui, has_buffer, &mut result);
       -                self.commands.crop.ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
       +                self.commands.as_ref().unwrap().crop.ui(ui, &mut result);
                    });
                    ui.menu_button(fl!(crate::LANGUAGE_LOADER, "menu-colors"), |ui| {
                        ui.style_mut().wrap = Some(false);
                        ui.set_min_width(300.0);
                        if has_buffer {
       -                    if let Some(pane) = self.get_active_pane() {
       +                    if let Some(pane) = self.get_active_pane_mut() {
                                if let Ok(doc) = &mut pane.doc.lock() {
                                    let editor = doc.get_ansi_editor_mut().unwrap();
                                    let lock = &mut editor.buffer_view.lock();
       @@ -388,35 +442,57 @@ impl MainWindow {
                            ui.separator();
                        }
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .select_palette
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .open_palettes_directory
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        ui.separator();
        
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .pick_attribute_under_caret
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .toggle_color
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .switch_to_default_color
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                    });
                    ui.menu_button(fl!(crate::LANGUAGE_LOADER, "menu-fonts"), |ui| {
                        ui.style_mut().wrap = Some(false);
                        ui.set_min_width(220.0);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .open_font_selector
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .open_font_manager
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        ui.separator();
       -                self.commands.open_font_directory.ui(ui, &mut result);
       -                self.commands.open_tdf_directory.ui(ui, &mut result);
       +                self.commands
       +                    .as_ref()
       +                    .unwrap()
       +                    .open_font_directory
       +                    .ui(ui, &mut result);
       +                self.commands
       +                    .as_ref()
       +                    .unwrap()
       +                    .open_tdf_directory
       +                    .ui(ui, &mut result);
                    });
                    ui.menu_button(fl!(crate::LANGUAGE_LOADER, "menu-view"), |ui| {
                        ui.style_mut().wrap = Some(false);
       @@ -434,10 +510,14 @@ impl MainWindow {
                                ui.style_mut().wrap = Some(false);
                                ui.set_min_width(270.0);
        
       -                        self.commands.zoom_reset.ui(ui, &mut result);
       -                        self.commands.zoom_in.ui(ui, &mut result);
       +                        self.commands
       +                            .as_ref()
       +                            .unwrap()
       +                            .zoom_reset
       +                            .ui(ui, &mut result);
       +                        self.commands.as_ref().unwrap().zoom_in.ui(ui, &mut result);
        
       -                        self.commands.zoom_out.ui(ui, &mut result);
       +                        self.commands.as_ref().unwrap().zoom_out.ui(ui, &mut result);
                                ui.separator();
        
                                if ui.button("4:1 400%").clicked() {
       @@ -555,21 +635,39 @@ impl MainWindow {
                            }
                        });
        
       -                self.commands.show_layer_borders.ui(ui, &mut result);
       -                self.commands.show_line_numbers.ui(ui, &mut result);
       +                self.commands
       +                    .as_ref()
       +                    .unwrap()
       +                    .show_layer_borders
       +                    .ui(ui, &mut result);
       +                self.commands
       +                    .as_ref()
       +                    .unwrap()
       +                    .show_line_numbers
       +                    .ui(ui, &mut result);
        
       -                self.commands.fullscreen.ui(ui, &mut result);
       +                self.commands
       +                    .as_ref()
       +                    .unwrap()
       +                    .fullscreen
       +                    .ui(ui, &mut result);
        
                        ui.separator();
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .set_reference_image
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .toggle_reference_image
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                        self.commands
       +                    .as_ref()
       +                    .unwrap()
                            .clear_reference_image
       -                    .ui_enabled(ui, has_buffer, &mut result);
       +                    .ui(ui, &mut result);
                    });
        
                    unsafe {
       @@ -591,7 +689,11 @@ impl MainWindow {
                                }
        
                                ui.separator();
       -                        self.commands.open_plugin_directory.ui(ui, &mut result);
       +                        self.commands
       +                            .as_ref()
       +                            .unwrap()
       +                            .open_plugin_directory
       +                            .ui(ui, &mut result);
                            });
                        }
                    }
       @@ -621,7 +723,7 @@ impl MainWindow {
                            ui.close_menu();
                        }
                        ui.separator();
       -                self.commands.about.ui(ui, &mut result);
       +                self.commands.as_ref().unwrap().about.ui(ui, &mut result);
                    });
                    self.top_bar_ui(ui, frame);
                });