kr580 1.0.0

Desktop KR580VM80 / Intel 8080 emulator.
Documentation
//! Button styles. Each helper picks a backing colour and border based on
//! the button's interaction status, plus optional accent / selected
//! flags for the more elaborate variants.

use iced::widget::button;
use iced::{Background, Border, Color};

use super::super::theme::{
    TOKYO_BOARD, TOKYO_BORDER, TOKYO_MUTED, TOKYO_RED, TOKYO_SURFACE, TOKYO_SURFACE_2,
    TOKYO_SURFACE_3, TOKYO_TEXT,
};

fn is_button_active(status: button::Status) -> bool {
    matches!(status, button::Status::Hovered | button::Status::Pressed)
}

/// Action-panel chrome (Run, Step, Reset…). Neutral border at rest;
/// only the surface tone shifts on hover/press. Per-button identity
/// comes from the SVG glyph's accent tint.
///
/// `Disabled` is a separate visual branch: surface stays at the
/// resting `TOKYO_BOARD` tone but the border drops to a low-alpha
/// tint and text fades to `TOKYO_MUTED`. The glyph itself is greyed
/// out separately by the chip widget when `message` is `None` –
/// border + text fade here is what tells the *frame* that the chip
/// is locked.
pub(crate) fn action_button_style(status: button::Status) -> button::Style {
    let disabled = matches!(status, button::Status::Disabled);
    let background = match status {
        button::Status::Pressed => TOKYO_SURFACE_2,
        button::Status::Hovered => TOKYO_SURFACE,
        _ => TOKYO_BOARD,
    };
    let border_color = if disabled {
        Color {
            a: 0.35,
            ..TOKYO_BORDER
        }
    } else {
        TOKYO_BORDER
    };
    let text_color = if disabled { TOKYO_MUTED } else { TOKYO_TEXT };

    button::Style {
        background: Some(Background::Color(background)),
        text_color,
        border: Border {
            radius: 6.0.into(),
            width: 1.0,
            color: border_color,
        },
        ..button::Style::default()
    }
}

/// Style for the `↵` apply buttons next to each editor field. Visually
/// matches the surrounding text inputs: same background colour, same
/// border radius, neutral border that does not light up on hover. The
/// only feedback is a slightly lighter surface tint when the cursor
/// hovers, with a touch more contrast on press.
pub(crate) fn enter_button_style(status: button::Status) -> button::Style {
    let background = match status {
        button::Status::Pressed => TOKYO_SURFACE_2,
        button::Status::Hovered => TOKYO_SURFACE,
        _ => TOKYO_BOARD,
    };

    button::Style {
        background: Some(Background::Color(background)),
        text_color: TOKYO_TEXT,
        border: Border {
            radius: 6.0.into(),
            width: 1.0,
            color: TOKYO_BORDER,
        },
        ..button::Style::default()
    }
}

pub(crate) fn modal_field_button_style(status: button::Status) -> button::Style {
    let background = match status {
        button::Status::Hovered => Color {
            a: 0.45,
            ..TOKYO_SURFACE
        },
        button::Status::Pressed => TOKYO_SURFACE_2,
        _ => Color::TRANSPARENT,
    };

    button::Style {
        background: Some(Background::Color(background)),
        text_color: TOKYO_TEXT,
        border: Border {
            radius: 6.0.into(),
            width: 1.0,
            color: TOKYO_BORDER,
        },
        ..button::Style::default()
    }
}

pub(crate) fn modal_tab_button_style(status: button::Status, active: bool) -> button::Style {
    let background = match (active, status) {
        (true, _) => TOKYO_SURFACE,
        (false, button::Status::Hovered) => Color {
            a: 0.45,
            ..TOKYO_SURFACE
        },
        (false, button::Status::Pressed) => TOKYO_SURFACE_2,
        _ => Color::TRANSPARENT,
    };

    button::Style {
        background: Some(Background::Color(background)),
        text_color: TOKYO_TEXT,
        border: Border {
            radius: 6.0.into(),
            width: 1.0,
            color: TOKYO_BORDER,
        },
        ..button::Style::default()
    }
}

pub(crate) fn modal_dropdown_option_style(
    status: button::Status,
    highlighted: bool,
) -> button::Style {
    let background = match (highlighted, status) {
        (true, _) => TOKYO_SURFACE,
        (false, button::Status::Hovered) => Color {
            a: 0.45,
            ..TOKYO_SURFACE
        },
        (false, button::Status::Pressed) => TOKYO_SURFACE_2,
        _ => Color::TRANSPARENT,
    };

    button::Style {
        background: Some(Background::Color(background)),
        text_color: TOKYO_TEXT,
        border: Border {
            radius: 4.0.into(),
            width: 0.0,
            color: Color::TRANSPARENT,
        },
        ..button::Style::default()
    }
}

pub(crate) fn menu_button_style(status: button::Status) -> button::Style {
    let background = if is_button_active(status) {
        TOKYO_SURFACE_2
    } else {
        Color::TRANSPARENT
    };

    button::Style {
        background: Some(Background::Color(background)),
        text_color: TOKYO_TEXT,
        border: Border {
            radius: 6.0.into(),
            ..Border::default()
        },
        ..button::Style::default()
    }
}

/// Disabled variant of `menu_button_style`: same chrome but the row
/// never lights up on hover/press because we never publish an
/// `on_press` for it. Text colour is `TOKYO_MUTED` so the row reads
/// as "currently unavailable" while staying discoverable in the menu
/// (used by the "clear HLT flag" entry when the flip-flop is off).
pub(crate) fn menu_button_disabled_style(_status: button::Status) -> button::Style {
    button::Style {
        background: Some(Background::Color(Color::TRANSPARENT)),
        text_color: TOKYO_MUTED,
        border: Border {
            radius: 6.0.into(),
            ..Border::default()
        },
        ..button::Style::default()
    }
}

pub(crate) fn step_button_style(status: button::Status) -> button::Style {
    // Inline glyphs: transparent at rest, faint surface tint on
    // hover/press – no border so they don't read as detached chips.
    let background = if is_button_active(status) {
        Color::from_rgba8(0x36, 0x3B, 0x59, 0.45)
    } else {
        Color::TRANSPARENT
    };

    button::Style {
        background: Some(Background::Color(background)),
        text_color: TOKYO_TEXT,
        border: Border {
            radius: 3.0.into(),
            width: 0.0,
            color: Color::TRANSPARENT,
        },
        ..button::Style::default()
    }
}

pub(crate) fn opcode_option_style(status: button::Status, highlighted: bool) -> button::Style {
    let background = if highlighted || is_button_active(status) {
        TOKYO_SURFACE_3
    } else {
        Color::TRANSPARENT
    };

    button::Style {
        background: Some(Background::Color(background)),
        text_color: TOKYO_TEXT,
        border: Border::default(),
        ..button::Style::default()
    }
}

/// Style for the minimise / maximise caption buttons in the custom
/// title bar. Transparent at rest so the bar reads as a single
/// contiguous surface; a faint surface tint lights up on hover so the
/// caption zone still telegraphs interactivity. Mirrors the native
/// caption convention: no border, square corners would conflict with
/// the rest of the chrome so we keep the 4 px radius the menu uses.
pub(crate) fn caption_button_style(status: button::Status) -> button::Style {
    let background = match status {
        button::Status::Hovered => TOKYO_SURFACE_2,
        button::Status::Pressed => TOKYO_SURFACE_3,
        _ => Color::TRANSPARENT,
    };

    button::Style {
        background: Some(Background::Color(background)),
        text_color: TOKYO_TEXT,
        border: Border {
            radius: 4.0.into(),
            ..Border::default()
        },
        ..button::Style::default()
    }
}

/// Style for the close caption button. Same flat-by-default chrome as
/// `caption_button_style`, except the hover/press surface flares red
/// so the destructive action lands with the same affordance as the
/// native window manager's close glyph. We do not recolour the SVG
/// stroke itself – the red surface already reads "warning", and the
/// glyph stays legible against it.
pub(crate) fn close_caption_button_style(status: button::Status) -> button::Style {
    let background = match status {
        button::Status::Hovered => TOKYO_RED,
        button::Status::Pressed => Color {
            a: 0.85,
            ..TOKYO_RED
        },
        _ => Color::TRANSPARENT,
    };

    button::Style {
        background: Some(Background::Color(background)),
        text_color: TOKYO_TEXT,
        border: Border {
            radius: 4.0.into(),
            ..Border::default()
        },
        ..button::Style::default()
    }
}

#[cfg(test)]
mod tests {
    use super::super::super::theme::TOKYO_BOARD;
    use super::*;

    #[test]
    fn action_button_resting_background_matches_app_plate() {
        let active = action_button_style(button::Status::Active);
        let disabled = action_button_style(button::Status::Disabled);

        assert_eq!(active.background, Some(Background::Color(TOKYO_BOARD)));
        assert_eq!(disabled.background, Some(Background::Color(TOKYO_BOARD)));
    }

    #[test]
    fn enter_button_resting_background_matches_app_plate() {
        let style = enter_button_style(button::Status::Active);

        assert_eq!(style.background, Some(Background::Color(TOKYO_BOARD)));
    }
}