kr580 1.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use super::{
    DesktopApp, ImportFileFormat, ImportModalFocus, MEMORY_SCROLL_VISIBLE_TICKS, Message,
    StatusKind,
};
use crate::backend::AppCommand;
use crate::i18n::Key;
use crate::persistence::Importers;
use iced::Task;
use std::path::PathBuf;

impl DesktopApp {
    pub(crate) fn open_import_modal(&mut self) {
        self.import_modal_open = true;
        self.import_modal_focus = ImportModalFocus::Browse;
        self.import_file_path = None;
        self.import_file_display.clear();
        self.import_file_format = None;
        self.import_target_options.clear();
        self.import_target_input.clear();
        self.import_target_dropdown_open = false;
        self.import_target_highlight = None;
        self.import_target_scroll_visible_ticks = 0;
        self.import_error = None;
        self.open_menu = None;
        self.hide_opcode_dropdown();
    }

    pub(crate) fn close_import_modal(&mut self) {
        self.import_modal_open = false;
        self.import_modal_focus = ImportModalFocus::Browse;
        self.import_target_dropdown_open = false;
        self.import_target_highlight = None;
        self.import_target_scroll_visible_ticks = 0;
    }

    pub(crate) fn route_import_modal_message(
        &mut self,
        message: &Message,
    ) -> Option<Task<Message>> {
        if !self.import_modal_open {
            return None;
        }

        match message {
            Message::Tick | Message::CursorMoved(_) | Message::ModifiersChanged(_) => None,
            Message::ImportFileBrowse => {
                self.choose_import_file();
                Some(Task::none())
            }
            Message::ImportTargetDropdownToggled => {
                self.toggle_import_target_dropdown();
                Some(Task::none())
            }
            Message::ImportTargetSelected(value) => {
                self.select_import_target(value.clone());
                Some(Task::none())
            }
            Message::ImportTargetScrolled => {
                self.import_target_scroll_visible_ticks = MEMORY_SCROLL_VISIBLE_TICKS;
                Some(Task::none())
            }
            Message::ConfirmImport => Some(self.confirm_import()),
            Message::CancelImport => {
                self.close_import_modal();
                Some(Task::none())
            }
            Message::EscPressed => {
                self.close_import_modal();
                Some(Task::none())
            }
            Message::MousePressedIgnored => {
                self.import_target_dropdown_open = false;
                self.import_target_highlight = None;
                self.import_target_scroll_visible_ticks = 0;
                self.import_modal_focus = ImportModalFocus::None;
                Some(Task::none())
            }
            Message::FocusCycle { backward } => {
                self.cycle_import_modal_focus(*backward);
                Some(Task::none())
            }
            Message::ArrowKey(direction) if self.import_target_dropdown_open => {
                self.move_import_target_highlight(*direction);
                Some(Task::none())
            }
            Message::EnterPressed if self.import_target_dropdown_open => {
                self.submit_import_target_dropdown();
                Some(Task::none())
            }
            Message::EnterPressed => Some(self.submit_import_modal_focus()),
            _ => Some(Task::none()),
        }
    }

    pub(crate) fn load_import_file(&mut self, path: PathBuf) {
        self.import_file_display = path.display().to_string();
        self.import_file_format = Some(ImportFileFormat::from_path(&path));
        self.import_file_path = Some(path.clone());
        self.import_error = None;
        self.import_target_dropdown_open = false;
        self.import_target_highlight = None;
        self.import_target_scroll_visible_ticks = 0;

        let targets = match self.import_file_format {
            Some(ImportFileFormat::Xlsx) => Importers::xlsx_sheet_names(&path),
            Some(ImportFileFormat::Text) => Importers::txt_section_names(&path),
            None => Ok(Vec::new()),
        };

        match targets {
            Ok(targets) => {
                self.import_target_options = targets;
                self.import_target_input = self
                    .import_target_options
                    .first()
                    .cloned()
                    .unwrap_or_default();
                self.import_modal_focus = if self.import_target_options.is_empty() {
                    ImportModalFocus::Confirm
                } else {
                    ImportModalFocus::Target
                };
            }
            Err(err) => {
                self.import_target_options.clear();
                self.import_target_input.clear();
                self.import_modal_focus = ImportModalFocus::Browse;
                self.import_error = Some(crate::runtime::humanize_error::humanize(
                    &err.to_string(),
                    self.lang,
                ));
            }
        }
    }

    pub(crate) fn confirm_import(&mut self) -> Task<Message> {
        let Some(path) = self.import_file_path.clone() else {
            self.import_error = Some(self.lang.t(Key::ImportChooseFileRequired).to_owned());
            self.import_modal_focus = ImportModalFocus::Browse;
            return Task::none();
        };
        let display = self.import_file_display.clone();
        let target = self.import_target_input.trim().to_owned();
        let command = match self
            .import_file_format
            .unwrap_or_else(|| ImportFileFormat::from_path(&path))
        {
            ImportFileFormat::Xlsx if !target.is_empty() => {
                AppCommand::ImportXlsxSheet(path, target)
            }
            ImportFileFormat::Xlsx => AppCommand::ImportXlsx(path),
            ImportFileFormat::Text if !target.is_empty() => {
                AppCommand::ImportTxtSection(path, target)
            }
            ImportFileFormat::Text => AppCommand::ImportTxt(path),
        };

        self.close_import_modal();
        self.clear_error_notice();
        self.running = false;
        self.dispatch_sync(command);
        if self.error_notice.is_some() {
            return Task::none();
        }
        self.undo_stack.clear();
        self.mark_saved();
        self.set_status(StatusKind::ImportFrom { display });
        Task::none()
    }

    fn choose_import_file(&mut self) {
        if let Some(path) = rfd::FileDialog::new()
            .add_filter("KR580 file", &["txt", "xlsx"])
            .add_filter("KR580 txt file", &["txt"])
            .add_filter("KR580 spreadsheet file", &["xlsx"])
            .pick_file()
        {
            self.load_import_file(path);
        }
    }

    fn toggle_import_target_dropdown(&mut self) {
        if self.import_target_options.is_empty() {
            return;
        }
        self.import_target_dropdown_open = !self.import_target_dropdown_open;
        self.import_target_highlight = if self.import_target_dropdown_open {
            self.import_target_scroll_visible_ticks = MEMORY_SCROLL_VISIBLE_TICKS;
            self.import_target_options
                .iter()
                .position(|option| option == &self.import_target_input)
                .or(Some(0))
        } else {
            self.import_target_scroll_visible_ticks = 0;
            None
        };
        self.import_modal_focus = ImportModalFocus::Target;
    }

    fn select_import_target(&mut self, value: String) {
        self.import_target_input = value;
        self.import_target_dropdown_open = false;
        self.import_target_highlight = None;
        self.import_target_scroll_visible_ticks = 0;
        self.import_modal_focus = ImportModalFocus::Target;
    }

    fn cycle_import_modal_focus(&mut self, backward: bool) {
        let mut next = if backward {
            self.import_modal_focus.previous()
        } else {
            self.import_modal_focus.next()
        };
        if self.import_target_options.is_empty() && next == ImportModalFocus::Target {
            next = if backward {
                ImportModalFocus::Browse
            } else {
                ImportModalFocus::Cancel
            };
        }
        self.import_modal_focus = next;
    }

    fn submit_import_modal_focus(&mut self) -> Task<Message> {
        match self.import_modal_focus {
            ImportModalFocus::Browse => {
                self.choose_import_file();
                Task::none()
            }
            ImportModalFocus::Target => {
                self.toggle_import_target_dropdown();
                Task::none()
            }
            ImportModalFocus::Cancel => {
                self.close_import_modal();
                Task::none()
            }
            ImportModalFocus::Confirm => self.confirm_import(),
            ImportModalFocus::None => Task::none(),
        }
    }

    fn move_import_target_highlight(&mut self, direction: i32) {
        let len = self.import_target_options.len();
        if len == 0 {
            self.import_target_highlight = None;
            return;
        }
        let current = self.import_target_highlight.unwrap_or(0) as i32;
        let next = current - direction;
        if next < 0 || next >= len as i32 {
            return;
        }
        self.import_target_highlight = Some(next as usize);
    }

    fn submit_import_target_dropdown(&mut self) {
        let Some(index) = self.import_target_highlight else {
            self.import_target_dropdown_open = false;
            return;
        };
        if let Some(value) = self.import_target_options.get(index).cloned() {
            self.select_import_target(value);
        }
    }
}