use iced::widget::{Space, column, container, mouse_area, row};
use iced::{Element, Length, Padding, alignment};
use k580_core::{RegisterName, decode_opcode};
use super::chips::{
FunctionalBlockState, device_chip, flag_strip, functional_block, schematic_mnemonic_readout,
schematic_readout, schematic_wide_readout,
};
use super::icons;
use super::lamps::control_lamps;
use super::mux::MuxRegisterValues;
use super::styles::{schematic_block_style, schematic_board_style};
use super::theme::{
TOKYO_BLUE, TOKYO_CYAN, TOKYO_GREEN, TOKYO_MAGENTA, TOKYO_MUTED, TOKYO_RED, TOKYO_TEXT,
TOKYO_YELLOW, mono_text, ui_text,
};
use super::tooltips::shortcut_hint;
use super::widgets::{LEGEND_LINE_OFFSET, legend_panel_left};
use crate::app::{DesktopApp, Message, RegisterInlineTarget};
use crate::i18n::Key;
const MAIN_TO_BOTTOM_SPACING: f32 = 8.0;
const LEFT_BOARD_SECTION_SPACING: f32 = 12.0;
const CENTRAL_COLUMN_SECTION_SPACING: f32 = LEFT_BOARD_SECTION_SPACING + LEGEND_LINE_OFFSET;
const CENTRAL_STATUS_REGISTER_SPACING_TRIM: f32 = 4.0;
const CENTRAL_STATUS_REGISTER_SPACING: f32 =
CENTRAL_COLUMN_SECTION_SPACING - CENTRAL_STATUS_REGISTER_SPACING_TRIM;
const FULLSCREEN_SCHEMATIC_MIN_HEIGHT: f32 = 900.0;
const FULLSCREEN_SCHEMATIC_COLUMN_GAP: f32 = 72.0;
impl DesktopApp {
pub(super) fn schematic_panel(&self) -> Element<'_, Message> {
let cpu = &self.snapshot.cpu;
let lang = self.lang;
let halt_indicator = mouse_area(mono_text(
if cpu.halted {
lang.t(Key::HltOn)
} else {
lang.t(Key::HltOff)
},
13,
if cpu.halted { TOKYO_RED } else { TOKYO_GREEN },
))
.on_press(Message::ToggleHalt)
.interaction(iced::mouse::Interaction::Pointer);
let status_row = row![
mono_text(format!("PC {:04X}", cpu.pc), 13, TOKYO_BLUE),
mono_text(format!("SP {:04X}", cpu.sp), 13, TOKYO_CYAN),
mono_text(format!("T {}", cpu.cycle_count), 13, TOKYO_YELLOW),
halt_indicator,
]
.spacing(14)
.align_y(alignment::Vertical::Center);
let flag_panel = container(flag_strip(cpu))
.padding([10, 20])
.width(Length::Fill)
.height(Length::Fixed(60.0))
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Center)
.style(schematic_block_style);
let psw_row = row![
schematic_readout(
"PSW",
format!(
"{:04X}",
((cpu.registers.a as u16) << 8) | cpu.flags.to_psw() as u16,
),
TOKYO_GREEN,
Some(lang.t(Key::PswTooltip)),
),
flag_panel,
]
.spacing(18)
.align_y(alignment::Vertical::Center);
let accumulator_target = RegisterInlineTarget::Schematic(RegisterName::A);
let buffer1_target = RegisterInlineTarget::Schematic(RegisterName::B);
let buffer2_target = RegisterInlineTarget::Schematic(RegisterName::C);
let active_target = self.active_register_target;
let inline_target = self.inline_register_target;
let hovered_target = self.hovered_register_target;
let inline_placeholder = self.input_placeholder(crate::app::REGISTER_INLINE_INPUT_ID, "00");
let registers_grid = column![
row![
functional_block(
lang.t(Key::Accumulator),
self.display_register_value(RegisterName::A),
TOKYO_GREEN,
accumulator_target,
FunctionalBlockState {
selected: active_target == Some(accumulator_target),
editing: inline_target == Some(accumulator_target),
hovered: hovered_target == Some(accumulator_target),
},
&self.register_value_input,
inline_placeholder,
),
Space::new().width(Length::Fill),
functional_block(
lang.t(Key::BufferRegister1),
self.display_register_value(RegisterName::B),
TOKYO_GREEN,
buffer1_target,
FunctionalBlockState {
selected: active_target == Some(buffer1_target),
editing: inline_target == Some(buffer1_target),
hovered: hovered_target == Some(buffer1_target),
},
&self.register_value_input,
inline_placeholder,
),
Space::new().width(Length::Fill),
functional_block(
lang.t(Key::BufferRegister2),
self.display_register_value(RegisterName::C),
TOKYO_GREEN,
buffer2_target,
FunctionalBlockState {
selected: active_target == Some(buffer2_target),
editing: inline_target == Some(buffer2_target),
hovered: hovered_target == Some(buffer2_target),
},
&self.register_value_input,
inline_placeholder,
),
]
.spacing(14),
row![
schematic_readout(
lang.t(Key::AddressBuffer),
format!("{:04X}", cpu.last_address_bus),
TOKYO_GREEN,
Some(lang.t(Key::AddressBufferTooltip)),
),
Space::new().width(Length::Fill),
schematic_readout(
lang.t(Key::InstructionRegister),
format!("{:02X}", cpu.last_fetched_opcode),
TOKYO_GREEN,
Some(lang.t(Key::InstructionRegisterTooltip)),
),
Space::new().width(Length::Fill),
schematic_mnemonic_readout(
lang.t(Key::InstructionDecoder),
decode_opcode(cpu.last_fetched_opcode)
.map(|info| info.mnemonic)
.unwrap_or_else(|_| "-".to_owned()),
TOKYO_GREEN,
Some(lang.t(Key::InstructionDecoderTooltip)),
),
]
.spacing(14),
]
.spacing(14);
let registers_panel = legend_panel_left(
lang.t(Key::RegistersAndOperands),
registers_grid,
Length::Shrink,
);
let cycles = super::cycles::cycle_panels(cpu, lang);
let signals_panel = legend_panel_left(
lang.t(Key::ControlSignals),
container(control_lamps(cpu, lang))
.width(Length::Fill)
.align_x(alignment::Horizontal::Center),
Length::Shrink,
);
let current_command_panel = legend_panel_left(
lang.t(Key::CurrentCommand),
super::current_command::current_command_panel(cpu, lang),
Length::Shrink,
);
let left_board = column![
psw_row,
registers_panel,
cycles,
signals_panel,
current_command_panel
]
.spacing(LEFT_BOARD_SECTION_SPACING)
.width(Length::Fixed(520.0));
let fullscreen_layout = self.window_maximized
|| self.main_window_size.height >= FULLSCREEN_SCHEMATIC_MIN_HEIGHT;
let status_register_block = super::status_register::status_register_tooltip(
cpu,
schematic_wide_readout(
lang.t(Key::StatusRegister),
super::status_register::status_register_bits(cpu),
TOKYO_GREEN,
None,
),
lang,
);
let flag_bits = format!(
"{}{}{}{} {}{}{}{}",
u8::from(cpu.flags.sign),
u8::from(cpu.flags.zero),
0,
u8::from(cpu.flags.auxiliary_carry),
0,
u8::from(cpu.flags.parity),
1,
u8::from(cpu.flags.carry),
);
let shortened_status =
crate::app::shorten_status_for_width(&self.status, self.main_window_size.width);
let status_value: Element<'_, Message> = mono_text(shortened_status, 13, TOKYO_TEXT)
.wrapping(iced::widget::text::Wrapping::None)
.into();
let status_chip = row![
ui_text(lang.t(Key::HeaderStatus), 12, TOKYO_MUTED),
status_value,
]
.spacing(12)
.align_y(alignment::Vertical::Center);
let header_row = row![
status_row,
container(status_chip)
.width(Length::Fill)
.align_x(alignment::Horizontal::Right)
.clip(true),
]
.spacing(20)
.align_y(alignment::Vertical::Center)
.width(Length::Fill);
let central_column = column![
schematic_wide_readout(
lang.t(Key::DataBuffer),
format!("{:02X}", cpu.last_data_bus_byte),
TOKYO_GREEN,
Some(lang.t(Key::DataBufferTooltip)),
),
schematic_wide_readout(
lang.t(Key::FlagsRegister),
flag_bits,
TOKYO_GREEN,
Some(lang.t(Key::FlagsRegisterTooltip)),
),
column![
super::mux::mux_panel(
cpu,
self.selected_register,
self.inline_register_target,
self.active_register_target,
self.hovered_register_target,
&self.register_value_input,
inline_placeholder,
MuxRegisterValues {
b: self.display_register_value(RegisterName::B),
c: self.display_register_value(RegisterName::C),
d: self.display_register_value(RegisterName::D),
e: self.display_register_value(RegisterName::E),
h: self.display_register_value(RegisterName::H),
l: self.display_register_value(RegisterName::L),
},
lang,
!self.register_name_input.is_empty(),
self.running,
),
status_register_block,
]
.spacing(CENTRAL_STATUS_REGISTER_SPACING)
.width(Length::Fill),
]
.spacing(CENTRAL_COLUMN_SECTION_SPACING)
.width(Length::Fixed(240.0));
let schematic_body = container(
row![left_board, central_column]
.spacing(if fullscreen_layout {
FULLSCREEN_SCHEMATIC_COLUMN_GAP
} else {
20.0
})
.align_y(alignment::Vertical::Top),
)
.width(Length::Fill)
.height(Length::Fill)
.align_x(alignment::Horizontal::Center)
.align_y(if fullscreen_layout {
alignment::Vertical::Center
} else {
alignment::Vertical::Top
});
let top = container(
column![header_row, schematic_body]
.spacing(12)
.height(Length::Fill),
)
.padding(12)
.width(Length::Fill)
.height(Length::Fill)
.style(schematic_block_style);
let devices = row![
device_chip(
icons::device_monitor(),
TOKYO_GREEN,
lang.t(Key::DeviceMonitor),
Some(Message::OpenMonitor),
shortcut_hint(&Message::OpenMonitor),
),
device_chip(
icons::device_floppy(),
TOKYO_CYAN,
lang.t(Key::DeviceFloppy),
Some(Message::OpenFloppy),
shortcut_hint(&Message::OpenFloppy),
),
device_chip(
icons::device_hdd(),
TOKYO_BLUE,
lang.t(Key::DeviceHdd),
Some(Message::OpenHdd),
shortcut_hint(&Message::OpenHdd),
),
device_chip(
icons::device_network(),
TOKYO_YELLOW,
lang.t(Key::DeviceNetwork),
Some(Message::OpenNetwork),
shortcut_hint(&Message::OpenNetwork),
),
device_chip(
icons::device_printer(),
TOKYO_MAGENTA,
lang.t(Key::DevicePrinter),
Some(Message::OpenPrinter),
shortcut_hint(&Message::OpenPrinter),
),
]
.spacing(14)
.align_y(alignment::Vertical::Center);
let quick_access = container(legend_panel_left(
lang.t(Key::QuickAccess),
container(devices)
.width(Length::Fill)
.align_x(alignment::Horizontal::Center),
Length::Shrink,
))
.width(Length::Fixed(290.0));
let bottom = row![
quick_access,
Space::new().width(Length::Fill),
super::speed::speed_panel(self.speed_tier, self.lang),
]
.spacing(24)
.align_y(alignment::Vertical::Bottom);
let content = column![top, bottom]
.spacing(MAIN_TO_BOTTOM_SPACING)
.height(Length::Fill);
container(content)
.padding(Padding {
top: 4.0,
right: 4.0,
bottom: 0.0,
left: 0.0,
})
.width(Length::Fill)
.height(Length::Fill)
.style(schematic_board_style)
.into()
}
}
#[cfg(test)]
mod tests {
#[test]
fn central_column_status_gap_and_height_are_fixed() {
assert_eq!(
super::CENTRAL_COLUMN_SECTION_SPACING,
super::LEFT_BOARD_SECTION_SPACING + super::LEGEND_LINE_OFFSET
);
assert_eq!(super::CENTRAL_STATUS_REGISTER_SPACING_TRIM, 4.0);
assert_eq!(super::super::chips::SCHEMATIC_WIDE_READOUT_HEIGHT, 60.0);
assert_eq!(super::FULLSCREEN_SCHEMATIC_MIN_HEIGHT, 900.0);
assert_eq!(super::FULLSCREEN_SCHEMATIC_COLUMN_GAP, 72.0);
}
}