kr580 1.1.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
use super::dropdown::{self, DropdownControl, DropdownItem};
use super::labels::{Label, label, localized_status};
use super::styles::{footer_button, group_style, radio_style};
use crate::app::{Message, PrinterSetupDialog, PrinterSetupDropdown, PrinterSetupFocus};
use crate::i18n::Lang;
use crate::view::styles::legend_label_style;
use crate::view::theme::{tokyo_border, tokyo_muted, tokyo_text, ui_text};
use iced::widget::{Space, column, container, radio, row, stack};
use iced::{Alignment, Border, Element, Length, Padding, alignment};
use k580_ui::devices::printer::{PrinterOrientation, PrinterPaper, PrinterSource};

pub(super) fn printer_section<'a>(
    dialog: &'a PrinterSetupDialog,
    lang: Lang,
) -> Element<'a, Message> {
    let selected_name = dialog.selected_name.as_deref();
    let options = dialog
        .printers
        .iter()
        .map(|printer| DropdownItem {
            selected: selected_name == Some(printer.name.as_str()),
            label: printer.name.clone(),
            message: Message::PrinterSetupSelected(printer.name.clone()),
        })
        .collect::<Vec<_>>();
    let selector = dropdown::control(
        selected_name.map(str::to_owned).unwrap_or_else(|| {
            label(
                lang,
                if dialog.loading {
                    Label::Loading
                } else {
                    Label::SelectPrinter
                },
            )
            .to_owned()
        }),
        options,
        DropdownControl {
            opened: dialog.open_dropdown == Some(PrinterSetupDropdown::Printer),
            enabled: !dialog.loading,
            focused: dialog.focus_is_visible(PrinterSetupFocus::Printer),
            toggle: Message::PrinterSetupDropdownToggled(PrinterSetupDropdown::Printer),
            dismiss: Message::PrinterSetupDropdownDismissed(PrinterSetupDropdown::Printer),
            highlighted: dialog.dropdown_highlight,
        },
    );
    let properties_ready = dialog.configuration.is_some()
        && !dialog.configuration_loading
        && !dialog.properties_pending;
    let properties = footer_button(
        label(lang, Label::Properties),
        properties_ready,
        dialog.focus_is_visible(PrinterSetupFocus::Properties),
    )
    .width(Length::Fixed(132.0))
    .on_press_maybe(properties_ready.then_some(Message::PrinterSetupProperties));
    let name_row = row![field_label(label(lang, Label::Name)), selector, properties]
        .spacing(10)
        .align_y(Alignment::Center);

    let details = match dialog.selected_printer() {
        Some(printer) => {
            let place = if printer.location.trim().is_empty() {
                &printer.port
            } else {
                &printer.location
            };
            column![
                detail_row(
                    label(lang, Label::Status),
                    localized_status(&printer.status, lang),
                ),
                detail_row(label(lang, Label::Type), &printer.driver),
                detail_row(label(lang, Label::Place), place),
                detail_row(label(lang, Label::Comment), &printer.comment),
            ]
            .spacing(7)
        }
        None => column![detail_row(
            label(lang, Label::Status),
            label(lang, Label::NoSelection),
        )],
    };
    column![name_row, details].spacing(12).into()
}

pub(super) fn paper_section<'a>(
    dialog: &'a PrinterSetupDialog,
    lang: Lang,
) -> Element<'a, Message> {
    let Some(configuration) = dialog.configuration.as_ref() else {
        return loading_content(dialog, lang);
    };
    let paper = configuration.selected_paper().cloned();
    let source = configuration.selected_source().cloned();
    column![
        row![
            field_label(label(lang, Label::Size)),
            paper_dropdown(dialog, configuration.papers.clone(), paper),
        ]
        .spacing(10)
        .align_y(Alignment::Center),
        row![
            field_label(label(lang, Label::Source)),
            source_dropdown(dialog, configuration.sources.clone(), source),
        ]
        .spacing(10)
        .align_y(Alignment::Center),
    ]
    .spacing(14)
    .into()
}

pub(super) fn orientation_section<'a>(
    dialog: &'a PrinterSetupDialog,
    lang: Lang,
) -> Element<'a, Message> {
    let Some(configuration) = dialog.configuration.as_ref() else {
        return loading_content(dialog, lang);
    };
    let selected = Some(configuration.settings.orientation);
    let controls = column![
        radio(
            label(lang, Label::Portrait),
            PrinterOrientation::Portrait,
            selected,
            Message::PrinterSetupOrientationSelected,
        )
        .size(18)
        .spacing(8)
        .text_size(13)
        .style(radio_style(
            dialog.focus_is_visible(PrinterSetupFocus::Portrait),
        )),
        radio(
            label(lang, Label::Landscape),
            PrinterOrientation::Landscape,
            selected,
            Message::PrinterSetupOrientationSelected,
        )
        .size(18)
        .spacing(8)
        .text_size(13)
        .style(radio_style(
            dialog.focus_is_visible(PrinterSetupFocus::Landscape),
        )),
    ]
    .spacing(12);
    container(
        row![paper_icon(configuration.settings.orientation), controls]
            .spacing(22)
            .align_y(Alignment::Center),
    )
    .padding(Padding {
        top: 0.0,
        right: 0.0,
        bottom: 8.0,
        left: 0.0,
    })
    .height(Length::Fill)
    .align_y(alignment::Vertical::Center)
    .into()
}

pub(super) fn group_box<'a>(
    title: &'static str,
    content: Element<'a, Message>,
    height: Length,
) -> container::Container<'a, Message> {
    let panel = container(content)
        .padding(Padding {
            top: 20.0,
            right: 14.0,
            bottom: 12.0,
            left: 14.0,
        })
        .width(Length::Fill)
        .height(height)
        .style(group_style);
    let legend = row![
        Space::new().width(Length::Fixed(12.0)),
        container(ui_text(title, 13, tokyo_text()))
            .padding([0, 6])
            .style(legend_label_style),
        Space::new().width(Length::Fill),
    ];
    container(
        stack![
            column![Space::new().height(Length::Fixed(9.0)), panel].height(height),
            legend,
        ]
        .width(Length::Fill)
        .height(height),
    )
    .height(height)
}

fn paper_dropdown(
    dialog: &PrinterSetupDialog,
    options: Vec<PrinterPaper>,
    selected: Option<PrinterPaper>,
) -> Element<'static, Message> {
    let selected_id = selected.as_ref().map(|paper| paper.id);
    let label = selected.map_or_else(|| "".to_owned(), |paper| paper.to_string());
    let items = options
        .into_iter()
        .map(|paper| DropdownItem {
            selected: Some(paper.id) == selected_id,
            label: paper.to_string(),
            message: Message::PrinterSetupPaperSelected(paper.id),
        })
        .collect();
    dropdown::control(
        label,
        items,
        DropdownControl {
            opened: dialog.open_dropdown == Some(PrinterSetupDropdown::Paper),
            enabled: !dialog.configuration_loading,
            focused: dialog.focus_is_visible(PrinterSetupFocus::Paper),
            toggle: Message::PrinterSetupDropdownToggled(PrinterSetupDropdown::Paper),
            dismiss: Message::PrinterSetupDropdownDismissed(PrinterSetupDropdown::Paper),
            highlighted: dialog.dropdown_highlight,
        },
    )
}

fn source_dropdown(
    dialog: &PrinterSetupDialog,
    options: Vec<PrinterSource>,
    selected: Option<PrinterSource>,
) -> Element<'static, Message> {
    let selected_id = selected.as_ref().map(|source| source.id);
    let label = selected.map_or_else(|| "".to_owned(), |source| source.to_string());
    let items = options
        .into_iter()
        .map(|source| DropdownItem {
            selected: Some(source.id) == selected_id,
            label: source.to_string(),
            message: Message::PrinterSetupSourceSelected(source.id),
        })
        .collect();
    dropdown::control(
        label,
        items,
        DropdownControl {
            opened: dialog.open_dropdown == Some(PrinterSetupDropdown::Source),
            enabled: !dialog.configuration_loading,
            focused: dialog.focus_is_visible(PrinterSetupFocus::Source),
            toggle: Message::PrinterSetupDropdownToggled(PrinterSetupDropdown::Source),
            dismiss: Message::PrinterSetupDropdownDismissed(PrinterSetupDropdown::Source),
            highlighted: dialog.dropdown_highlight,
        },
    )
}

fn loading_content<'a>(dialog: &PrinterSetupDialog, lang: Lang) -> Element<'a, Message> {
    container(ui_text(
        label(
            lang,
            if dialog.configuration_loading {
                Label::LoadingSettings
            } else {
                Label::NoSelection
            },
        ),
        12,
        tokyo_muted(),
    ))
    .width(Length::Fill)
    .height(Length::Fill)
    .align_x(alignment::Horizontal::Center)
    .align_y(alignment::Vertical::Center)
    .into()
}

fn detail_row<'a>(label: &'static str, value: &'a str) -> Element<'a, Message> {
    let value = if value.trim().is_empty() {
        ""
    } else {
        value
    };
    row![
        ui_text(label, 12, tokyo_muted()).width(Length::Fixed(150.0)),
        ui_text(value.to_owned(), 13, tokyo_text()),
    ]
    .spacing(8)
    .align_y(Alignment::Center)
    .into()
}

fn field_label(label: &'static str) -> Element<'static, Message> {
    ui_text(label, 13, tokyo_text())
        .width(Length::Fixed(78.0))
        .into()
}

fn paper_icon(orientation: PrinterOrientation) -> Element<'static, Message> {
    let (width, height) = match orientation {
        PrinterOrientation::Portrait => (54.0, 68.0),
        PrinterOrientation::Landscape => (68.0, 54.0),
    };
    let paper = container(ui_text("A", 28, tokyo_text()))
        .width(Length::Fixed(width))
        .height(Length::Fixed(height))
        .align_x(alignment::Horizontal::Center)
        .align_y(alignment::Vertical::Center)
        .style(|_theme| container::Style {
            border: Border {
                radius: 2.0.into(),
                width: 1.0,
                color: tokyo_border(),
            },
            ..container::Style::default()
        });
    container(paper)
        .width(Length::Fixed(72.0))
        .height(Length::Fixed(72.0))
        .align_x(alignment::Horizontal::Center)
        .align_y(alignment::Vertical::Center)
        .into()
}