kr580 2.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use iced::{Subscription, Task, event, keyboard, mouse, time};

use super::handlers::tick_interval;
use super::messages::Message;
use super::shortcuts::{binding_from_event, shortcut_message};
use super::state::DesktopApp;
use crate::persistence::ShortcutSettings;

impl DesktopApp {
    pub(crate) fn subscription(&self) -> Subscription<Message> {
        let mut subscriptions = vec![
            time::every(tick_interval(self.running, self.speed_tier)).map(|_| Message::Tick),
            iced::window::close_events().map(Message::WindowClosed),
            event::listen_with(|event, status, window| {
                Some(Message::RuntimeEvent {
                    event,
                    status,
                    window,
                })
            }),
        ];

        if self.startup_frames_seen < 2 || self.settings_dialog.is_some() || self.file_drag_hovered
        {
            subscriptions.push(iced::window::frames().map(|_| Message::FrameRendered));
        }

        Subscription::batch(subscriptions)
    }

    pub(crate) fn handle_runtime_event(
        &mut self,
        event: iced::Event,
        status: event::Status,
        window: iced::window::Id,
    ) -> Task<Message> {
        if let Some(task) = self.handle_file_drag_event(&event, window) {
            return task;
        }
        runtime_event_message(self, event, status, window)
            .map(Task::done)
            .unwrap_or_else(Task::none)
    }
}

fn runtime_event_message(
    app: &DesktopApp,
    event: iced::Event,
    status: event::Status,
    window: iced::window::Id,
) -> Option<Message> {
    if (app.export_modal_open || app.import_modal_open)
        && let iced::Event::Keyboard(keyboard::Event::KeyPressed {
            key: keyboard::Key::Named(keyboard::key::Named::Tab),
            modifiers,
            ..
        }) = &event
        && (*modifiers == keyboard::Modifiers::default()
            || *modifiers == keyboard::Modifiers::SHIFT)
    {
        return Some(Message::FocusCycle {
            backward: modifiers.shift(),
        });
    }
    if app.top_menu_focus.is_some()
        && let iced::Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) = &event
        && let Some(message) = super::menu_keyboard::navigation_key_message(key, *modifiers)
    {
        return Some(message);
    }
    let recording_shortcut = app
        .settings_dialog
        .as_ref()
        .and_then(|dialog| dialog.recording_shortcut)
        .is_some();
    match (event, status) {
        (iced::Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)), _) => {
            Some(Message::ModifiersChanged(modifiers))
        }
        (
            iced::Event::Keyboard(keyboard::Event::KeyPressed {
                key: keyboard::Key::Named(keyboard::key::Named::Escape),
                ..
            }),
            _,
        ) if recording_shortcut => Some(Message::SettingsShortcutCaptureCancelled),
        (
            iced::Event::Keyboard(keyboard::Event::KeyPressed {
                physical_key,
                modifiers,
                ..
            }),
            _,
        ) if recording_shortcut => {
            binding_from_event(physical_key, modifiers).map(Message::SettingsShortcutCaptured)
        }
        (
            iced::Event::Keyboard(keyboard::Event::KeyPressed {
                key: keyboard::Key::Named(keyboard::key::Named::Escape),
                ..
            }),
            _,
        ) => Some(Message::EscPressed),
        (
            iced::Event::Keyboard(keyboard::Event::KeyPressed {
                key,
                physical_key,
                modifiers,
                ..
            }),
            status,
        ) if modifiers.command() => command_shortcut_message(
            &app.shortcut_settings,
            &key,
            physical_key,
            modifiers,
            status,
        ),
        (
            iced::Event::Keyboard(keyboard::Event::KeyPressed {
                physical_key,
                modifiers,
                ..
            }),
            _,
        ) if modifiers.alt() => shortcut_message(&app.shortcut_settings, physical_key, modifiers),
        (
            iced::Event::Keyboard(keyboard::Event::KeyPressed {
                key,
                physical_key,
                modifiers,
                ..
            }),
            iced::event::Status::Ignored,
        ) => super::menu_keyboard::navigation_key_message(&key, modifiers).or_else(|| match key {
            keyboard::Key::Named(keyboard::key::Named::PageUp) => {
                Some(Message::MemoryAddressPageUp)
            }
            keyboard::Key::Named(keyboard::key::Named::PageDown) => {
                Some(Message::MemoryAddressPageDown)
            }
            keyboard::Key::Named(keyboard::key::Named::F1) => Some(Message::OpenHelp),
            _ => shortcut_message(&app.shortcut_settings, physical_key, modifiers),
        }),
        (
            iced::Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }),
            iced::event::Status::Captured,
        ) => captured_register_arrow(&key, modifiers),
        (iced::Event::Mouse(mouse::Event::CursorMoved { position }), _) => {
            Some(Message::CursorMoved(position))
        }
        (
            iced::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
            iced::event::Status::Ignored,
        ) => Some(Message::MousePressedIgnored),
        (iced::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)), _) => {
            Some(Message::MousePressed)
        }
        (iced::Event::Window(iced::window::Event::CloseRequested), _) => {
            Some(Message::WindowCloseRequested(window))
        }
        (iced::Event::Window(iced::window::Event::Resized(size)), _) => {
            Some(Message::WindowResized { id: window, size })
        }
        _ => None,
    }
}

fn captured_register_arrow(key: &keyboard::Key, modifiers: keyboard::Modifiers) -> Option<Message> {
    if modifiers.command() || modifiers.alt() || modifiers.shift() {
        return None;
    }
    let direction = match key {
        keyboard::Key::Named(keyboard::key::Named::ArrowUp) => super::RegisterMove::Up,
        keyboard::Key::Named(keyboard::key::Named::ArrowDown) => super::RegisterMove::Down,
        keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => super::RegisterMove::Left,
        keyboard::Key::Named(keyboard::key::Named::ArrowRight) => super::RegisterMove::Right,
        _ => return None,
    };
    Some(Message::RegisterArrowKey(direction))
}

fn command_shortcut_message(
    settings: &ShortcutSettings,
    key: &keyboard::Key,
    physical_key: keyboard::key::Physical,
    modifiers: keyboard::Modifiers,
    status: event::Status,
) -> Option<Message> {
    if let Some(direction) = super::register_inline::ctrl_arrow_move(key, modifiers) {
        return Some(Message::RegisterArrowKey(direction));
    }
    if let keyboard::Key::Named(keyboard::key::Named::Tab) = key {
        return Some(Message::SettingsSectionCycle {
            backward: modifiers.shift(),
        });
    }
    if matches!(status, event::Status::Captured)
        && (is_text_select_all_shortcut(key, physical_key, modifiers)
            || is_text_paste_shortcut(key, physical_key, modifiers))
    {
        return None;
    }
    let configured = shortcut_message(settings, physical_key, modifiers);
    if configured.is_some() {
        return configured;
    }
    if matches!(status, event::Status::Ignored)
        && is_text_paste_shortcut(key, physical_key, modifiers)
    {
        return Some(Message::PasteMemoryBytesRequested);
    }
    None
}

fn is_text_select_all_shortcut(
    key: &keyboard::Key,
    physical_key: keyboard::key::Physical,
    modifiers: keyboard::Modifiers,
) -> bool {
    text_command_shortcut(key, physical_key, modifiers, 'a')
}

fn is_text_paste_shortcut(
    key: &keyboard::Key,
    physical_key: keyboard::key::Physical,
    modifiers: keyboard::Modifiers,
) -> bool {
    text_command_shortcut(key, physical_key, modifiers, 'v')
}

fn text_command_shortcut(
    key: &keyboard::Key,
    physical_key: keyboard::key::Physical,
    modifiers: keyboard::Modifiers,
    expected: char,
) -> bool {
    if modifiers.shift() || modifiers.alt() {
        return false;
    }
    key.to_latin(physical_key)
        .is_some_and(|latin| latin.eq_ignore_ascii_case(&expected))
}

#[cfg(test)]
mod tests;