kr580 1.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use std::path::PathBuf;

use iced::{Size, Task, window};

use super::{DesktopApp, Message, PendingAction, ToolWindowKind};
use crate::i18n::Key;
use crate::platform;

const ICON_PNG: &[u8] = include_bytes!("../../assets/icons/icon-64.png");
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct ToolWindowState {
    pub(crate) id: Option<iced::window::Id>,
    pub(crate) ready: bool,
    pub(crate) detached: bool,
    pub(crate) always_on_top: bool,
}

const TOOL_WINDOWS: [ToolWindowKind; 5] = [
    ToolWindowKind::Monitor,
    ToolWindowKind::Floppy,
    ToolWindowKind::Hdd,
    ToolWindowKind::Network,
    ToolWindowKind::Printer,
];

impl DesktopApp {
    pub(crate) fn tool_window(&self, kind: ToolWindowKind) -> &ToolWindowState {
        match kind {
            ToolWindowKind::Monitor => &self.monitor_window,
            ToolWindowKind::Floppy => &self.floppy_window,
            ToolWindowKind::Hdd => &self.hdd_window,
            ToolWindowKind::Network => &self.network_window,
            ToolWindowKind::Printer => &self.printer_window,
        }
    }

    pub(crate) fn tool_window_mut(&mut self, kind: ToolWindowKind) -> &mut ToolWindowState {
        match kind {
            ToolWindowKind::Monitor => &mut self.monitor_window,
            ToolWindowKind::Floppy => &mut self.floppy_window,
            ToolWindowKind::Hdd => &mut self.hdd_window,
            ToolWindowKind::Network => &mut self.network_window,
            ToolWindowKind::Printer => &mut self.printer_window,
        }
    }

    pub(crate) fn boot(initial: Option<PathBuf>) -> (Self, Task<Message>) {
        let (mut app, startup) = Self::with_initial_path(initial);
        let (id, open) = window::open(main_window_settings());
        app.main_window_id = Some(id);
        (app, Task::batch([startup, open.map(Message::WindowOpened)]))
    }

    pub(crate) fn title(&self, window: window::Id) -> String {
        self.tool_window_kind(window)
            .map(|kind| self.lang.t(tool_window_title(kind)).to_owned())
            .unwrap_or_else(|| "KR580 Emulator".to_owned())
    }

    pub(crate) fn dispatch_window_message(&mut self, message: &Message) -> Option<Task<Message>> {
        let task = match message {
            Message::WindowOpened(id) => self.window_opened(*id),
            Message::WindowClosed(id) => self.window_closed(*id),
            Message::WindowResized { id, size } => {
                if self.main_window_id == Some(*id) {
                    self.main_window_size = *size;
                }
                Task::none()
            }
            Message::FrameRendered => self.frame_rendered(),
            Message::WindowDragStart => self.drag_main_window(),
            Message::ToolWindowDragStart(kind) => self.drag_tool_window(*kind),
            Message::WindowMinimize => self
                .main_window_id
                .map_or_else(Task::none, |id| window::minimize(id, true)),
            Message::WindowToggleMaximize => self.toggle_main_window_maximized(),
            Message::WindowClose => self.main_window_id.map_or_else(Task::none, window::close),
            Message::WindowMaximizedChanged(maximized) => {
                self.window_maximized = *maximized;
                Task::none()
            }
            Message::WindowCloseRequested(id) => self.window_close_requested(*id),
            Message::DetachToolWindow(kind) => self.detach_tool_window(*kind),
            Message::AttachToolWindow(kind) => self.attach_tool_window(*kind),
            Message::ToggleToolWindowAlwaysOnTop(kind) => {
                self.toggle_tool_window_always_on_top(*kind)
            }
            _ => return None,
        };
        Some(task)
    }

    pub(crate) fn close_monitor(&mut self) -> Task<Message> {
        self.close_tool_window(ToolWindowKind::Monitor)
    }

    pub(crate) fn close_floppy(&mut self) -> Task<Message> {
        self.close_tool_window(ToolWindowKind::Floppy)
    }

    pub(crate) fn close_hdd(&mut self) -> Task<Message> {
        self.close_tool_window(ToolWindowKind::Hdd)
    }

    pub(crate) fn close_network(&mut self) -> Task<Message> {
        self.close_tool_window(ToolWindowKind::Network)
    }

    pub(crate) fn close_printer(&mut self) -> Task<Message> {
        self.close_tool_window(ToolWindowKind::Printer)
    }

    fn detach_tool_window(&mut self, kind: ToolWindowKind) -> Task<Message> {
        self.set_tool_window_open(kind, true);
        let state = self.tool_window_mut(kind);
        state.detached = true;
        if let Some(id) = state.id {
            return if state.ready {
                self.show_tool_window(kind, id)
            } else {
                Task::none()
            };
        }
        self.open_tool_window(kind)
    }

    fn attach_tool_window(&mut self, kind: ToolWindowKind) -> Task<Message> {
        self.set_tool_window_open(kind, true);
        let state = self.tool_window_mut(kind);
        state.detached = false;
        state.always_on_top = false;
        self.hide_or_close_tool_window(kind)
    }

    fn open_tool_window(&mut self, kind: ToolWindowKind) -> Task<Message> {
        if self.tool_window(kind).id.is_some() {
            return Task::none();
        }
        let (id, open) = window::open(tool_window_settings(kind, self.main_window_size));
        let state = self.tool_window_mut(kind);
        state.id = Some(id);
        state.ready = false;
        open.map(Message::WindowOpened)
    }

    fn show_tool_window(&self, kind: ToolWindowKind, id: window::Id) -> Task<Message> {
        window::resize(id, tool_window_size(kind, self.main_window_size))
            .chain(window::set_mode(id, window::Mode::Windowed))
            .chain(window::gain_focus(id))
    }

    fn hide_or_close_tool_window(&mut self, kind: ToolWindowKind) -> Task<Message> {
        let Some(id) = self.tool_window(kind).id else {
            return Task::none();
        };
        if platform::SUPPORTS_HIDDEN_WINDOW_REUSE {
            window::set_level(id, window::Level::Normal)
                .chain(window::set_mode(id, window::Mode::Hidden))
        } else {
            let state = self.tool_window_mut(kind);
            state.ready = false;
            state.id = None;
            window::close(id)
        }
    }

    fn toggle_tool_window_always_on_top(&mut self, kind: ToolWindowKind) -> Task<Message> {
        let state = self.tool_window_mut(kind);
        let Some(id) = state.id else {
            return Task::none();
        };
        state.always_on_top = !state.always_on_top;
        let level = if state.always_on_top {
            window::Level::AlwaysOnTop
        } else {
            window::Level::Normal
        };
        window::set_level(id, level)
    }

    fn window_opened(&mut self, id: window::Id) -> Task<Message> {
        if let Some(kind) = self.tool_window_kind(id) {
            let detached = {
                let state = self.tool_window_mut(kind);
                state.ready = true;
                state.detached
            };
            let prepare = iced::window::run(id, |window| {
                platform::set_rounded_corners(window);
            })
            .discard();
            return if detached {
                prepare.chain(self.show_tool_window(kind, id))
            } else {
                prepare
            };
        }
        if self.main_window_id != Some(id) {
            return Task::none();
        }
        Task::batch([
            iced::window::run(id, |window| platform::cloak_window(window, true)).discard(),
            iced::window::run(id, |window| platform::set_rounded_corners(window)).discard(),
            iced::window::set_mode(id, iced::window::Mode::Windowed),
            iced::window::is_maximized(id).map(Message::WindowMaximizedChanged),
        ])
    }

    fn window_closed(&mut self, id: window::Id) -> Task<Message> {
        if let Some(kind) = self.tool_window_kind(id) {
            *self.tool_window_mut(kind) = Default::default();
            self.reset_tool_window_presentation(kind);
            return Task::none();
        }
        if self.main_window_id != Some(id) {
            return Task::none();
        }
        self.main_window_id = None;
        let close_windows = TOOL_WINDOWS.into_iter().filter_map(|kind| {
            self.reset_tool_window_presentation(kind);
            self.tool_window_mut(kind).id.take().map(window::close)
        });
        Task::batch(close_windows).chain(iced::exit())
    }

    fn window_close_requested(&mut self, id: window::Id) -> Task<Message> {
        if let Some(kind) = self.tool_window_kind(id) {
            return self.close_tool_window(kind);
        }
        if self.main_window_id != Some(id) {
            return Task::none();
        }
        if self.dirty {
            self.open_discard_modal(PendingAction::CloseWindow);
            Task::none()
        } else {
            window::close(id)
        }
    }

    fn frame_rendered(&mut self) -> Task<Message> {
        if self.startup_frames_seen < u8::MAX {
            self.startup_frames_seen = self.startup_frames_seen.saturating_add(1);
        }
        if self.startup_frames_seen != 2 {
            return Task::none();
        }
        self.main_window_id.map_or_else(Task::none, |id| {
            iced::window::run(id, |window| platform::cloak_window(window, false)).discard()
        })
    }

    fn drag_main_window(&mut self) -> Task<Message> {
        if self.close_titlebar_popup_before_drag() {
            return Task::none();
        }
        self.main_window_id
            .map_or_else(Task::none, iced::window::drag)
    }

    fn drag_tool_window(&self, kind: ToolWindowKind) -> Task<Message> {
        self.tool_window(kind)
            .id
            .map_or_else(Task::none, window::drag)
    }

    fn toggle_main_window_maximized(&mut self) -> Task<Message> {
        let Some(id) = self.main_window_id else {
            return Task::none();
        };
        self.window_maximized = !self.window_maximized;
        Task::batch([
            iced::window::toggle_maximize(id),
            iced::window::is_maximized(id).map(Message::WindowMaximizedChanged),
        ])
    }

    fn close_tool_window(&mut self, kind: ToolWindowKind) -> Task<Message> {
        self.reset_tool_window_presentation(kind);
        self.hide_or_close_tool_window(kind)
    }

    fn reset_tool_window_presentation(&mut self, kind: ToolWindowKind) {
        self.set_tool_window_open(kind, false);
        let state = self.tool_window_mut(kind);
        state.detached = false;
        state.always_on_top = false;
        if kind == ToolWindowKind::Monitor {
            self.monitor_hex_popup = false;
        }
        if kind == ToolWindowKind::Network {
            self.network_settings_open = false;
            self.network_settings_error = None;
        }
    }

    fn set_tool_window_open(&mut self, kind: ToolWindowKind, open: bool) {
        match kind {
            ToolWindowKind::Monitor => self.monitor_open = open,
            ToolWindowKind::Floppy => self.floppy_open = open,
            ToolWindowKind::Hdd => self.hdd_open = open,
            ToolWindowKind::Network => self.network_open = open,
            ToolWindowKind::Printer => self.printer_open = open,
        }
    }

    fn tool_window_kind(&self, id: window::Id) -> Option<ToolWindowKind> {
        TOOL_WINDOWS
            .into_iter()
            .find(|kind| self.tool_window(*kind).id == Some(id))
    }
}

fn main_window_settings() -> window::Settings {
    window::Settings {
        size: Size::new(1180.0, 720.0),
        position: window::Position::Centered,
        min_size: Some(Size::new(1180.0, 720.0)),
        icon: window::icon::from_file_data(ICON_PNG, None).ok(),
        decorations: false,
        visible: false,
        exit_on_close_request: false,
        ..window::Settings::default()
    }
}

fn tool_window_settings(kind: ToolWindowKind, main_window_size: Size) -> window::Settings {
    let size = tool_window_size(kind, main_window_size);
    window::Settings {
        size,
        position: window::Position::Centered,
        min_size: Some(tool_window_min_size(kind)),
        icon: window::icon::from_file_data(ICON_PNG, None).ok(),
        decorations: false,
        visible: false,
        exit_on_close_request: false,
        ..window::Settings::default()
    }
}

fn tool_window_size(kind: ToolWindowKind, main_window_size: Size) -> Size {
    match kind {
        ToolWindowKind::Monitor => detached_monitor_size(main_window_size),
        ToolWindowKind::Floppy
        | ToolWindowKind::Hdd
        | ToolWindowKind::Network
        | ToolWindowKind::Printer => detached_storage_size(),
    }
}

fn tool_window_min_size(kind: ToolWindowKind) -> Size {
    match kind {
        ToolWindowKind::Monitor => Size::new(720.0, 480.0),
        ToolWindowKind::Floppy
        | ToolWindowKind::Hdd
        | ToolWindowKind::Network
        | ToolWindowKind::Printer => detached_storage_size(),
    }
}

fn tool_window_title(kind: ToolWindowKind) -> Key {
    match kind {
        ToolWindowKind::Monitor => Key::HnMonitor,
        ToolWindowKind::Floppy => Key::HnFloppy,
        ToolWindowKind::Hdd => Key::HnHdd,
        ToolWindowKind::Network => Key::HnNetwork,
        ToolWindowKind::Printer => Key::HnPrinter,
    }
}

pub(super) fn detached_monitor_size(main_window_size: Size) -> Size {
    const MODAL_INSET: f32 = 120.0;
    Size::new(
        (main_window_size.width - MODAL_INSET).max(720.0),
        (main_window_size.height - MODAL_INSET).max(480.0),
    )
}

pub(crate) fn detached_storage_size() -> Size {
    Size::new(760.0, 340.0)
}