kr580 2.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use iced::widget::{Space, column, container, mouse_area, opaque, row, stack, text};
use iced::{Element, Length, alignment};

use super::styles::{modal_backdrop_style, panel_style as modal_dialog_style};
use super::theme::{tokyo_muted, tokyo_red, tokyo_text, ui_text};
use super::widgets::{modal_footer_button_focused, shorten_middle, text_input_shell};
use crate::app::{Message, SubprogramDialogFocus, SubprogramDialogMode};
use crate::i18n::{Key, Lang};

pub(super) struct SubprogramModalViewState<'a> {
    pub(super) mode: SubprogramDialogMode,
    pub(super) focus: SubprogramDialogFocus,
    pub(super) keyboard_focus_visible: bool,
    pub(super) path: &'a std::path::Path,
    pub(super) start: &'a str,
    pub(super) end: &'a str,
    pub(super) error: Option<&'a str>,
    pub(super) lang: Lang,
}

pub(super) fn subprogram_modal_overlay<'a>(
    state: SubprogramModalViewState<'a>,
) -> Element<'a, Message> {
    let title = match state.mode {
        SubprogramDialogMode::Open => state.lang.t(Key::SubprogramTitleOpen),
        SubprogramDialogMode::Save => state.lang.t(Key::SubprogramTitleSave),
    };
    let confirm_label = match state.mode {
        SubprogramDialogMode::Open => state.lang.t(Key::FileOpen),
        SubprogramDialogMode::Save => state.lang.t(Key::FileSave),
    };
    let start_focused = state.keyboard_focus_visible && state.focus == SubprogramDialogFocus::Start;
    let end_focused = state.keyboard_focus_visible && state.focus == SubprogramDialogFocus::End;
    let cancel_focused =
        state.keyboard_focus_visible && state.focus == SubprogramDialogFocus::Cancel;
    let confirm_focused =
        state.keyboard_focus_visible && state.focus == SubprogramDialogFocus::Confirm;

    let mut fields = column![
        path_row(state.path, state.lang),
        field_row(
            state.lang.t(Key::SubprogramStartAddress),
            state.start,
            Message::SubprogramStartChanged,
            start_focused,
        ),
    ]
    .spacing(10);
    if matches!(state.mode, SubprogramDialogMode::Save) {
        fields = fields.push(field_row(
            state.lang.t(Key::SubprogramEndAddress),
            state.end,
            Message::SubprogramEndChanged,
            end_focused,
        ));
    }
    if let Some(error) = state.error {
        fields = fields.push(text(error).size(12).color(tokyo_red()));
    }

    let footer = row![
        Space::new().width(Length::Fill),
        modal_footer_button_focused(
            state.lang.t(Key::DiscardCancel),
            Message::CancelSubprogram,
            super::styles::modal_field_button_style,
            cancel_focused,
        ),
        modal_footer_button_focused(
            confirm_label,
            Message::ConfirmSubprogram,
            super::styles::modal_field_button_style,
            confirm_focused,
        ),
    ]
    .spacing(10);

    let dialog = container(
        column![
            ui_text(title, 16, tokyo_text()),
            ui_text(state.lang.t(Key::SubprogramAddressHint), 12, tokyo_muted(),),
            fields,
            footer,
        ]
        .spacing(12)
        .width(Length::Fixed(500.0)),
    )
    .padding(18)
    .style(modal_dialog_style);

    let backdrop = mouse_area(
        container(Space::new())
            .width(Length::Fill)
            .height(Length::Fill)
            .style(modal_backdrop_style),
    )
    .on_press(Message::CancelSubprogram);
    let centred = column![
        Space::new().height(Length::Fill),
        row![
            Space::new().width(Length::Fill),
            opaque(dialog),
            Space::new().width(Length::Fill),
        ]
        .width(Length::Fill),
        Space::new().height(Length::Fill),
    ]
    .width(Length::Fill)
    .height(Length::Fill);

    stack![opaque(backdrop), centred]
        .width(Length::Fill)
        .height(Length::Fill)
        .into()
}

fn path_row(path: &std::path::Path, lang: Lang) -> Element<'static, Message> {
    row![
        container(ui_text(lang.t(Key::ImportFileLabel), 13, tokyo_text()))
            .width(Length::Fixed(132.0))
            .align_y(alignment::Vertical::Center),
        container(ui_text(
            shorten_middle(&path.display().to_string(), 48),
            13,
            tokyo_text(),
        ))
        .width(Length::Fill)
        .padding([6, 8])
        .style(super::styles::inset_style),
    ]
    .spacing(8)
    .align_y(alignment::Vertical::Center)
    .into()
}

fn field_row<'a>(
    label: &'static str,
    value: &'a str,
    on_input: fn(String) -> Message,
    focused: bool,
) -> Element<'a, Message> {
    let input = text_input_shell("", value, on_input, Length::Fill);
    row![
        container(ui_text(label, 13, tokyo_text()))
            .width(Length::Fixed(132.0))
            .align_y(alignment::Vertical::Center),
        container(input).width(Length::Fill).style(move |_theme| {
            let mut style = super::styles::inset_style(_theme);
            if focused {
                style.border.color = tokyo_text();
            }
            style
        }),
    ]
    .spacing(8)
    .align_y(alignment::Vertical::Center)
    .into()
}