Introduces a VirtualClock-based timing system replacing wall-clock Instant-based timing for hardware-accurate simulation. New modules: - virtual_clock.rs: Shared virtual time source with condvar sync - real_time_governor.rs: Thread that advances virtual time - component_timing.rs: Per-component timing constraints Key changes: - Component trait: added update_at_time(), timing(), set_virtual_clock_optional() - All Intel 400x components migrated to virtual-time-aware operation - ConfigurableSystem: start() returns Result, spawns governor thread - GUI: timing controls (speed slider, mode selector, step button) - Console: speed/mode/step/time commands - Backward compatible: legacy fallback for components without VirtualClock Reviewed-by: code review found 2 warnings (clock resume() dead code, reset() safety docs) — both fixed.
1224 lines
47 KiB
Rust
1224 lines
47 KiB
Rust
//! # GUI Components Module
|
||
//!
|
||
//! This module implements all the individual GUI components for the Intel MCS-4 emulator.
|
||
//! Each component is responsible for rendering its specific section of the interface
|
||
//! and handling user interactions within that section.
|
||
//!
|
||
//! ## Component Architecture
|
||
//!
|
||
//! The GUI is organized into several specialized components:
|
||
//!
|
||
//! - **`GuiComponents`**: Main container managing all components
|
||
//! - **`ControlPanel`**: System control buttons and actions
|
||
//! - **`MemoryViewer`**: RAM content display and inspection
|
||
//! - **`RegisterViewer`**: CPU register state visualization
|
||
//! - **`RomLoader`**: File dialog integration for ROM loading
|
||
//! - **`StatusBar`**: System status and component health display
|
||
//!
|
||
//! ## Design Principles
|
||
//!
|
||
//! ### Separation of Concerns
|
||
//! Each component handles only its specific functionality:
|
||
//! - State management is centralized in `GuiState`
|
||
//! - Components receive immutable references where possible
|
||
//! - User interactions trigger state changes through controlled interfaces
|
||
//!
|
||
//! ### Performance Optimization
|
||
//! - Components minimize allocations and cloning
|
||
//! - State updates are batched and efficient
|
||
//! - UI layout uses egui's immediate mode for optimal performance
|
||
//!
|
||
//! ### User Experience
|
||
//! - Consistent visual design across all components
|
||
//! - Clear visual feedback for user actions
|
||
//! - Intuitive layout and component organization
|
||
//!
|
||
//! ## Component Communication
|
||
//!
|
||
//! Components communicate through the shared `GuiState`:
|
||
//! - Read-only access to state for display components
|
||
//! - Controlled mutation through specific methods
|
||
//! - Thread-safe state sharing with the emulator system
|
||
//!
|
||
//! ## Error Handling
|
||
//!
|
||
//! Components handle errors gracefully:
|
||
//! - Invalid states are displayed to users
|
||
//! - Failed operations show clear error messages
|
||
//! - Recovery options are provided where applicable
|
||
|
||
use super::state::GuiState;
|
||
use crate::system_config::{ConfigurableSystem, SystemController};
|
||
use crate::virtual_clock::SimulationMode;
|
||
use eframe::egui;
|
||
use parking_lot::Mutex;
|
||
use std::sync::Arc;
|
||
|
||
/// Container for all GUI components
|
||
///
|
||
/// This structure manages the lifecycle and coordination of all GUI components.
|
||
/// It acts as a facade, providing a clean interface for rendering different
|
||
/// sections of the user interface while maintaining proper separation of concerns.
|
||
///
|
||
/// ## Component Management
|
||
///
|
||
/// The container is responsible for:
|
||
/// - Component initialization and configuration
|
||
/// - Render orchestration and layout management
|
||
/// - State distribution to appropriate components
|
||
/// - Component lifecycle and cleanup
|
||
///
|
||
/// ## Performance Considerations
|
||
///
|
||
/// - Components are created once and reused across frames
|
||
/// - State references are passed efficiently without cloning
|
||
/// - Layout is managed centrally for consistent spacing
|
||
pub struct GuiComponents {
|
||
/// System control and management interface
|
||
control_panel: ControlPanel,
|
||
/// Timing/speed/mode/step controls
|
||
timing_controls: TimingControls,
|
||
/// RAM content inspection and visualization
|
||
memory_viewer: MemoryViewer,
|
||
/// ROM content inspection and visualization
|
||
rom_viewer: RomViewer,
|
||
/// CPU register state display
|
||
register_viewer: RegisterViewer,
|
||
/// ROM file loading and management
|
||
#[allow(dead_code)] // RomLoader is currently a placeholder implementation
|
||
rom_loader: RomLoader,
|
||
/// System status and health monitoring
|
||
status_bar: StatusBar,
|
||
}
|
||
|
||
impl GuiComponents {
|
||
/// Create a new GUI components container with all components initialized
|
||
///
|
||
/// This constructor sets up all GUI components with their default configurations.
|
||
/// Components are ready to use immediately after creation.
|
||
///
|
||
/// # Returns
|
||
/// A fully initialized `GuiComponents` instance
|
||
///
|
||
/// # Example
|
||
/// ```rust,no_run
|
||
/// use rusty_emu::gui::components::GuiComponents;
|
||
///
|
||
/// let mut components = GuiComponents::new();
|
||
/// // Components are ready to render
|
||
/// ```
|
||
pub fn new() -> Self {
|
||
Self {
|
||
control_panel: ControlPanel::new(),
|
||
timing_controls: TimingControls::new(),
|
||
memory_viewer: MemoryViewer::new(),
|
||
rom_viewer: RomViewer::new(),
|
||
register_viewer: RegisterViewer::new(),
|
||
rom_loader: RomLoader::new(),
|
||
status_bar: StatusBar::new(),
|
||
}
|
||
}
|
||
|
||
/// Render the control panel component
|
||
///
|
||
/// The control panel provides system management functionality including
|
||
/// start/stop/reset controls and ROM loading capabilities.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Mutable reference to GUI state for control operations
|
||
/// * `system` - Optional reference to the emulator system for actual control
|
||
pub fn render_control_panel(
|
||
&mut self,
|
||
ui: &mut egui::Ui,
|
||
state: &mut GuiState,
|
||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||
) {
|
||
self.control_panel.render(ui, state, system);
|
||
}
|
||
|
||
/// Render the timing controls component
|
||
///
|
||
/// Displays simulation speed, mode, step controls, and virtual time.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Mutable reference to GUI state for display and interaction
|
||
/// * `system` - Optional reference to the emulator system for clock control
|
||
pub fn render_timing_controls(
|
||
&mut self,
|
||
ui: &mut egui::Ui,
|
||
state: &mut GuiState,
|
||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||
) {
|
||
self.timing_controls.render(ui, state, system);
|
||
}
|
||
|
||
/// Render the memory viewer component
|
||
///
|
||
/// Displays RAM contents in a tabular format with bank selection
|
||
/// and hex/decimal viewing options.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Mutable reference to GUI state for display and interaction
|
||
pub fn render_memory_viewer(&self, ui: &mut egui::Ui, state: &mut GuiState) {
|
||
self.memory_viewer.render(ui, state);
|
||
}
|
||
|
||
/// Render the ROM viewer component
|
||
///
|
||
/// Displays ROM contents in a tabular format with address selection
|
||
/// and hex/decimal viewing options.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Mutable reference to GUI state for display and interaction
|
||
pub fn render_rom_viewer(&self, ui: &mut egui::Ui, state: &mut GuiState) {
|
||
self.rom_viewer.render(ui, state);
|
||
}
|
||
|
||
/// Render the register viewer component
|
||
///
|
||
/// Shows CPU register state including accumulator, program counter,
|
||
/// index registers, and system flags.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Mutable reference to GUI state for display and interaction
|
||
pub fn render_register_viewer(&self, ui: &mut egui::Ui, state: &mut GuiState) {
|
||
self.register_viewer.render(ui, state);
|
||
}
|
||
|
||
/// Render the status bar component
|
||
///
|
||
/// Displays system health, component status, cycle counts,
|
||
/// and any error conditions.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Immutable reference to GUI state for display
|
||
pub fn render_status_bar(&self, ui: &mut egui::Ui, state: &GuiState) {
|
||
self.status_bar.render(ui, state);
|
||
}
|
||
}
|
||
|
||
/// Control panel component for system control buttons and operations
|
||
///
|
||
/// The control panel provides the primary interface for managing the emulator system.
|
||
/// It offers intuitive controls for starting, stopping, and resetting the emulation,
|
||
/// along with system configuration and file management capabilities.
|
||
///
|
||
/// ## Features
|
||
///
|
||
/// - **System Lifecycle Management**: Start, stop, and reset emulator execution
|
||
/// - **Visual State Feedback**: Dynamic button states reflecting current system status
|
||
/// - **Error Handling**: Clear error display and recovery mechanisms
|
||
/// - **Application Control**: Window management and application termination
|
||
///
|
||
/// ## Button States
|
||
///
|
||
/// The control panel adapts its interface based on system state:
|
||
/// - **Stopped**: Shows "Start System" button, disabled stop/reset buttons
|
||
/// - **Running**: Shows "System Running..." text, enabled stop/reset buttons
|
||
/// - **Error**: Displays error messages with option to clear them
|
||
pub struct ControlPanel {
|
||
/// Dynamic text for the start/stop button based on system state
|
||
start_button_text: String,
|
||
/// Whether the stop button should be enabled
|
||
stop_button_enabled: bool,
|
||
/// Whether the reset button should be enabled
|
||
reset_button_enabled: bool,
|
||
}
|
||
|
||
impl ControlPanel {
|
||
/// Create a new control panel with default state
|
||
///
|
||
/// Initializes the control panel in the "stopped" state with appropriate
|
||
/// button configurations for a system that's ready to start.
|
||
///
|
||
/// # Returns
|
||
/// A new `ControlPanel` instance ready for system control
|
||
pub fn new() -> Self {
|
||
Self {
|
||
start_button_text: "Start System".to_string(),
|
||
stop_button_enabled: false,
|
||
reset_button_enabled: false,
|
||
}
|
||
}
|
||
|
||
/// Render the control panel interface
|
||
///
|
||
/// Creates a horizontal layout with all control buttons and handles
|
||
/// user interactions. The interface adapts dynamically based on system state.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering and interaction
|
||
/// * `state` - Mutable reference to GUI state for system control
|
||
///
|
||
/// # Layout
|
||
/// ```text
|
||
/// ┌─────────────────────────────────────────────────┐
|
||
/// │ System Control ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ │
|
||
/// │ [Load ROM] [Start System] [Stop] [Reset] [Close] │
|
||
/// └─────────────────────────────────────────────────┘
|
||
/// ```
|
||
pub fn render(
|
||
&mut self,
|
||
ui: &mut egui::Ui,
|
||
state: &mut GuiState,
|
||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||
) {
|
||
ui.horizontal(|ui| {
|
||
// Section header
|
||
ui.heading("System Control");
|
||
|
||
// ROM loading button
|
||
if ui.button("Load ROM").clicked() {
|
||
// ROM loading will be handled by RomLoader component
|
||
state.set_error("ROM loader not yet implemented".to_string());
|
||
}
|
||
|
||
// Start/Stop system button (context-sensitive)
|
||
if ui.button(&self.start_button_text).clicked() {
|
||
if !state.system_running {
|
||
self.start_system(state, system);
|
||
}
|
||
}
|
||
|
||
// Stop system button (only enabled when running)
|
||
if ui.button("Stop System").clicked() && state.system_running {
|
||
self.stop_system(state, system);
|
||
}
|
||
|
||
// Reset system button
|
||
if ui.button("Reset System").clicked() {
|
||
self.reset_system(state, system);
|
||
}
|
||
|
||
// Application close button
|
||
if ui.button("Close").clicked() {
|
||
// Stop the system before closing if available
|
||
if let Some(sys) = system {
|
||
let mut sys_guard = sys.lock();
|
||
// Use the SystemController trait method explicitly
|
||
let _ = SystemController::stop(&mut *sys_guard);
|
||
}
|
||
// Close application - handled by eframe
|
||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||
}
|
||
});
|
||
|
||
ui.separator();
|
||
}
|
||
|
||
/// Start the emulator system
|
||
///
|
||
/// Transitions the system from stopped to running state and updates
|
||
/// all related UI elements to reflect the new state.
|
||
///
|
||
/// # Arguments
|
||
/// * `state` - Mutable reference to GUI state
|
||
/// * `system` - Optional reference to the emulator system
|
||
fn start_system(
|
||
&mut self,
|
||
state: &mut GuiState,
|
||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||
) {
|
||
// Actually start the system if available
|
||
if let Some(sys) = system {
|
||
let mut sys_guard = sys.lock();
|
||
// Use the SystemController trait method explicitly
|
||
let result = SystemController::start(&mut *sys_guard);
|
||
match result {
|
||
Ok(()) => {
|
||
state.system_running = true;
|
||
self.start_button_text = "System Running...".to_string();
|
||
self.stop_button_enabled = true;
|
||
self.reset_button_enabled = true;
|
||
state.clear_error();
|
||
}
|
||
Err(e) => {
|
||
state.set_error(format!("Failed to start system: {}", e));
|
||
}
|
||
}
|
||
} else {
|
||
// No system available - just update UI state
|
||
state.system_running = true;
|
||
self.start_button_text = "System Running...".to_string();
|
||
self.stop_button_enabled = true;
|
||
self.reset_button_enabled = true;
|
||
state.clear_error();
|
||
}
|
||
}
|
||
|
||
/// Stop the emulator system
|
||
///
|
||
/// Transitions the system from running to stopped state and updates
|
||
/// UI elements accordingly.
|
||
///
|
||
/// # Arguments
|
||
/// * `state` - Mutable reference to GUI state
|
||
/// * `system` - Optional reference to the emulator system
|
||
fn stop_system(
|
||
&mut self,
|
||
state: &mut GuiState,
|
||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||
) {
|
||
// Actually stop the system if available
|
||
if let Some(sys) = system {
|
||
let mut sys_guard = sys.lock();
|
||
// Use the SystemController trait method explicitly
|
||
let result = SystemController::stop(&mut *sys_guard);
|
||
match result {
|
||
Ok(()) => {
|
||
state.system_running = false;
|
||
self.start_button_text = "Start System".to_string();
|
||
self.stop_button_enabled = false;
|
||
state.clear_error();
|
||
}
|
||
Err(e) => {
|
||
state.set_error(format!("Failed to stop system: {}", e));
|
||
}
|
||
}
|
||
} else {
|
||
// No system available - just update UI state
|
||
state.system_running = false;
|
||
self.start_button_text = "Start System".to_string();
|
||
self.stop_button_enabled = false;
|
||
state.clear_error();
|
||
}
|
||
}
|
||
|
||
/// Reset the emulator system
|
||
///
|
||
/// Performs a complete system reset, stopping execution and clearing
|
||
/// all state including cycle counts and error conditions.
|
||
///
|
||
/// # Arguments
|
||
/// * `state` - Mutable reference to GUI state
|
||
/// * `system` - Optional reference to the emulator system
|
||
fn reset_system(
|
||
&mut self,
|
||
state: &mut GuiState,
|
||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||
) {
|
||
// Stop the system first if available
|
||
if let Some(sys) = system {
|
||
let mut sys_guard = sys.lock();
|
||
// Use the SystemController trait method explicitly
|
||
let _ = SystemController::stop(&mut *sys_guard);
|
||
}
|
||
// Reset UI state
|
||
state.system_running = false;
|
||
state.cycle_count = 0;
|
||
self.start_button_text = "Start System".to_string();
|
||
self.stop_button_enabled = false;
|
||
self.reset_button_enabled = false;
|
||
state.clear_error();
|
||
}
|
||
}
|
||
|
||
/// Timing controls component for simulation speed, mode, and step management.
|
||
///
|
||
/// This component provides controls for the virtual clock system:
|
||
/// - **Speed slider**: Logarithmic scale from 0.001× to 100×
|
||
/// - **Mode selector**: RealTime / MaxSpeed / SingleStep radio buttons
|
||
/// - **Step button**: Advance one tick in SingleStep mode
|
||
/// - **Virtual time display**: Current time, tick count, actual vs target speed
|
||
///
|
||
/// ## Logarithmic Slider Mapping
|
||
///
|
||
/// The slider position `t` (0.0 to 1.0) maps to speed via:
|
||
/// `speed = 10^(t * 5 - 3)` giving range [0.001, 100.0]
|
||
pub struct TimingControls {
|
||
/// Current slider position (0.0 to 1.0), mapped logarithmically to speed
|
||
speed_slider_pos: f32,
|
||
}
|
||
|
||
impl TimingControls {
|
||
/// Create a new timing controls component
|
||
pub fn new() -> Self {
|
||
// Default: 1.0x speed → log10(1.0) = 0, so t = (0 + 3) / 5 = 0.6
|
||
Self {
|
||
speed_slider_pos: 0.6,
|
||
}
|
||
}
|
||
|
||
/// Convert slider position (0..1) to speed multiplier using logarithmic scale.
|
||
///
|
||
/// Range: 0.001× (position 0.0) to 100.0× (position 1.0)
|
||
/// Formula: speed = 10^(pos * 5 - 3)
|
||
fn pos_to_speed(pos: f32) -> f64 {
|
||
let pos = pos.clamp(0.0, 1.0) as f64;
|
||
10f64.powf(pos * 5.0 - 3.0)
|
||
}
|
||
|
||
/// Convert speed multiplier to slider position (0..1) using logarithmic scale.
|
||
///
|
||
/// Formula: pos = (log10(speed) + 3) / 5
|
||
fn speed_to_pos(speed: f64) -> f32 {
|
||
if speed <= 0.0 {
|
||
return 0.0;
|
||
}
|
||
let pos = (speed.log10() + 3.0) / 5.0;
|
||
pos.clamp(0.0, 1.0) as f32
|
||
}
|
||
|
||
/// Render the timing controls interface.
|
||
///
|
||
/// # Layout
|
||
/// ```text
|
||
/// ┌──────────────────────────────────────────────────────────────────┐
|
||
/// │ Timing Controls │
|
||
/// │ Speed: [========●===========] 1.00× (actual: 0.98×) │
|
||
/// │ Mode: (●) RealTime ( ) MaxSpeed ( ) SingleStep [Step] │
|
||
/// │ Virtual Time: 1.23 ms | Ticks: 3,690 | Paused: No │
|
||
/// └──────────────────────────────────────────────────────────────────┘
|
||
/// ```
|
||
pub fn render(
|
||
&mut self,
|
||
ui: &mut egui::Ui,
|
||
state: &mut GuiState,
|
||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||
) {
|
||
// Sync slider position from actual clock speed so external changes
|
||
// (e.g., console "speed 0.1" command) are reflected in the GUI.
|
||
self.speed_slider_pos = Self::speed_to_pos(state.timing_state.target_speed);
|
||
|
||
ui.vertical(|ui| {
|
||
ui.heading("Timing Controls");
|
||
|
||
// Speed slider row
|
||
ui.horizontal(|ui| {
|
||
ui.label("Speed:");
|
||
let slider = egui::Slider::new(&mut self.speed_slider_pos, 0.0..=1.0)
|
||
.show_value(false)
|
||
.step_by(0.001);
|
||
let response = ui.add(slider);
|
||
|
||
if response.changed() {
|
||
let new_speed = Self::pos_to_speed(self.speed_slider_pos);
|
||
if let Some(sys) = system {
|
||
let sys_guard = sys.lock();
|
||
let clock = sys_guard.get_virtual_clock();
|
||
clock.set_speed_multiplier(new_speed);
|
||
// If in MaxSpeed mode, switch back to RealTime when slider moves
|
||
if clock.mode() == SimulationMode::MaxSpeed {
|
||
clock.set_mode(SimulationMode::RealTime);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Show current target speed
|
||
let speed_text = state.timing_state.format_speed();
|
||
ui.label(format!("Target: {}", speed_text));
|
||
|
||
// Show actual speed
|
||
let actual_text = state.timing_state.format_actual_speed();
|
||
ui.label(format!("Actual: {}", actual_text));
|
||
});
|
||
|
||
// Mode selector and step button row
|
||
ui.horizontal(|ui| {
|
||
ui.label("Mode:");
|
||
|
||
let current_mode = state.timing_state.mode.as_str();
|
||
let is_realtime = current_mode == "RealTime";
|
||
let is_maxspeed = current_mode == "MaxSpeed";
|
||
let is_singlestep = current_mode == "SingleStep";
|
||
|
||
// RealTime radio
|
||
if ui.radio(is_realtime, "RealTime").clicked() {
|
||
self.apply_mode(system, SimulationMode::RealTime);
|
||
}
|
||
// MaxSpeed radio
|
||
if ui.radio(is_maxspeed, "MaxSpeed").clicked() {
|
||
self.apply_mode(system, SimulationMode::MaxSpeed);
|
||
}
|
||
// SingleStep radio
|
||
if ui.radio(is_singlestep, "SingleStep").clicked() {
|
||
self.apply_mode(system, SimulationMode::SingleStep);
|
||
}
|
||
|
||
ui.separator();
|
||
|
||
// Step button (enabled only in SingleStep mode)
|
||
let step_pending = state.timing_state.step_pending;
|
||
|
||
let step_button = ui.add_enabled(
|
||
is_singlestep && !step_pending,
|
||
egui::Button::new("Step"),
|
||
);
|
||
|
||
if step_button.clicked() {
|
||
if let Some(sys) = system {
|
||
let sys_guard = sys.lock();
|
||
let clock = sys_guard.get_virtual_clock();
|
||
clock.trigger_step();
|
||
}
|
||
}
|
||
|
||
if is_singlestep && step_pending {
|
||
ui.colored_label(egui::Color32::YELLOW, "⏳ Step pending...");
|
||
}
|
||
});
|
||
|
||
// Virtual time display row
|
||
ui.horizontal(|ui| {
|
||
ui.label("Virtual Time:");
|
||
ui.strong(state.timing_state.format_virtual_time());
|
||
|
||
ui.separator();
|
||
|
||
ui.label("Ticks:");
|
||
ui.strong(format!("{}", state.timing_state.tick_count));
|
||
|
||
ui.separator();
|
||
|
||
// Paused indicator
|
||
if state.timing_state.paused {
|
||
ui.colored_label(egui::Color32::YELLOW, "⏸ Paused");
|
||
} else if state.system_running {
|
||
ui.colored_label(egui::Color32::GREEN, "▶ Running");
|
||
} else {
|
||
ui.colored_label(egui::Color32::RED, "⏹ Stopped");
|
||
}
|
||
});
|
||
});
|
||
|
||
ui.separator();
|
||
}
|
||
|
||
/// Apply a simulation mode change to the virtual clock.
|
||
fn apply_mode(
|
||
&mut self,
|
||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||
mode: SimulationMode,
|
||
) {
|
||
if let Some(sys) = system {
|
||
let sys_guard = sys.lock();
|
||
let clock = sys_guard.get_virtual_clock();
|
||
clock.set_mode(mode);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Memory viewer component for displaying RAM contents and state
|
||
///
|
||
/// The memory viewer provides comprehensive RAM inspection capabilities,
|
||
/// allowing users to examine memory contents across different banks with
|
||
/// flexible display options and intuitive navigation.
|
||
///
|
||
/// ## Features
|
||
///
|
||
/// - **Multi-Bank Display**: View RAM contents across 4 banks simultaneously
|
||
/// - **Flexible Formatting**: Toggle between hexadecimal and decimal display
|
||
/// - **Interactive Bank Selection**: Choose which memory bank to inspect
|
||
/// - **Scrollable Interface**: Navigate through memory contents efficiently
|
||
/// - **Real-time Updates**: Live memory content updates during emulation
|
||
///
|
||
/// ## Display Format
|
||
///
|
||
/// The memory viewer shows data in a structured grid:
|
||
/// - **Address Column**: Memory addresses in hexadecimal format
|
||
/// - **Bank Columns**: B0-B3 showing contents of each memory bank
|
||
/// - **Value Display**: Configurable hex or decimal representation
|
||
///
|
||
/// ## Memory Organization
|
||
///
|
||
/// The Intel 4002 RAM has the following structure:
|
||
/// - **4 Banks**: Independent memory banks (B0-B3)
|
||
/// - **4 Bytes per Bank**: Addresses 0x00-0x03 in each bank
|
||
/// - **4-bit Values**: Each memory location stores a 4-bit nibble
|
||
pub struct MemoryViewer {
|
||
/// Number of bytes to display per row (currently fixed at 16)
|
||
#[allow(dead_code)]
|
||
// bytes_per_row is currently fixed at 16 but kept for future flexibility
|
||
bytes_per_row: usize,
|
||
}
|
||
|
||
impl MemoryViewer {
|
||
/// Create a new memory viewer with default configuration
|
||
///
|
||
/// Initializes the viewer with standard layout optimized for the Intel 4002 RAM structure.
|
||
///
|
||
/// # Returns
|
||
/// A new `MemoryViewer` instance ready for RAM content display
|
||
pub fn new() -> Self {
|
||
Self { bytes_per_row: 16 }
|
||
}
|
||
|
||
/// Render the memory viewer interface
|
||
///
|
||
/// Creates a comprehensive memory inspection interface with bank selection,
|
||
/// display options, and a scrollable memory contents grid.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Mutable reference to GUI state containing memory data
|
||
///
|
||
/// # Layout Structure
|
||
/// ```text
|
||
/// ┌─────────────────────────────────────────────────┐
|
||
/// │ Memory Viewer [─] [□] │
|
||
/// │ Bank: [0] □ Hex View │
|
||
/// ├─────────────────────────────────────────────────┤
|
||
/// │ Address B0 B1 B2 B3 │
|
||
/// │ [00] [12] [34] [56] [78] │
|
||
/// │ [01] [9A] [BC] [DE] [F0] │
|
||
/// │ [02] [11] [22] [33] [44] │
|
||
/// │ [03] [55] [66] [77] [88] │
|
||
/// └─────────────────────────────────────────────────┘
|
||
/// ```
|
||
pub fn render(&self, ui: &mut egui::Ui, state: &mut GuiState) {
|
||
ui.vertical(|ui| {
|
||
// Section header
|
||
ui.heading("Memory Viewer");
|
||
|
||
// Control bar with bank selection and display options
|
||
ui.horizontal(|ui| {
|
||
ui.label("Bank:");
|
||
// Bank selector (0-3 for Intel 4002)
|
||
ui.add(
|
||
egui::DragValue::new(&mut state.memory_state.selected_bank).clamp_range(0..=3),
|
||
);
|
||
ui.checkbox(&mut state.memory_state.show_hex, "Hex View");
|
||
});
|
||
|
||
ui.separator();
|
||
|
||
// Memory contents in scrollable area
|
||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||
egui::Grid::new("memory_grid").striped(true).show(ui, |ui| {
|
||
// Header row with bank labels
|
||
ui.label("Address");
|
||
for i in 0..4 {
|
||
ui.label(format!("B{}", i));
|
||
}
|
||
ui.end_row();
|
||
|
||
// Memory contents rows
|
||
for addr in 0..4 {
|
||
// Address column
|
||
ui.label(format!("{:02X}", addr));
|
||
|
||
// Bank data columns
|
||
for bank in 0..4 {
|
||
let value = state.memory_state.ram_contents[bank][addr];
|
||
if state.memory_state.show_hex {
|
||
ui.label(format!("{:02X}", value));
|
||
} else {
|
||
ui.label(format!("{}", value));
|
||
}
|
||
}
|
||
ui.end_row();
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
ui.separator();
|
||
}
|
||
}
|
||
|
||
/// Register viewer component for displaying CPU register state
|
||
///
|
||
/// The register viewer provides comprehensive CPU state visualization,
|
||
/// showing all key registers, flags, and pointers that define the current
|
||
/// execution state of the Intel 4004 microprocessor.
|
||
///
|
||
/// ## Features
|
||
///
|
||
/// - **Complete Register Set**: Display all 16 index registers (R0-R15)
|
||
/// - **Core CPU State**: Accumulator, program counter, stack pointer
|
||
/// - **Flag Visualization**: Carry flag status with clear indicators
|
||
/// - **Interactive Selection**: Choose which index register to highlight
|
||
/// - **Dual Format Display**: Hexadecimal and decimal representations
|
||
/// - **Real-time Updates**: Live register state during emulation
|
||
///
|
||
/// ## Intel 4004 Register Architecture
|
||
///
|
||
/// The Intel 4004 has the following register structure:
|
||
/// - **Accumulator**: Main arithmetic register (4 bits)
|
||
/// - **Index Registers**: 16 general-purpose registers (R0-R15)
|
||
/// - **Program Counter**: 12-bit address pointer
|
||
/// - **Stack Pointer**: 4-bit stack address register
|
||
/// - **Carry Flag**: Arithmetic carry/borrow indicator
|
||
///
|
||
/// ## Display Organization
|
||
///
|
||
/// Registers are organized in a clear hierarchy:
|
||
/// - **Primary Registers**: Most frequently used (accumulator, PC, stack)
|
||
/// - **Flags**: System status indicators
|
||
/// - **Index Registers**: General-purpose register file
|
||
pub struct RegisterViewer {
|
||
// No internal state - all UI state is stored in GuiState
|
||
}
|
||
|
||
impl RegisterViewer {
|
||
/// Create a new register viewer with default configuration
|
||
///
|
||
/// Initializes the viewer with standard display formatting optimized
|
||
/// for the Intel 4004 architecture.
|
||
///
|
||
/// # Returns
|
||
/// A new `RegisterViewer` instance ready for CPU state display
|
||
pub fn new() -> Self {
|
||
Self {}
|
||
}
|
||
|
||
/// Render the register viewer interface
|
||
///
|
||
/// Creates a comprehensive register inspection interface with organized
|
||
/// display of all CPU state elements and interactive index register selection.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Mutable reference to GUI state containing register data
|
||
///
|
||
/// # Layout Structure
|
||
/// ```text
|
||
/// ┌─────────────────────────────────────────────────┐
|
||
/// │ CPU Registers [─] [□] │
|
||
/// │ Index Register: [0] │
|
||
/// ├─────────────────────────────────────────────────┤
|
||
/// │ Register │ Value (Hex) │ Value (Dec) │
|
||
/// │ Accumulator │ 0F │ 15 │
|
||
/// │ Carry Flag │ 1 │ Set │
|
||
/// │ Program Counter│ 123 │ 291 │
|
||
/// │ Index R0 │ 2A │ 42 │
|
||
/// │ Stack Pointer │ 08 │ 8 │
|
||
/// └─────────────────────────────────────────────────┘
|
||
/// ```
|
||
pub fn render(&self, ui: &mut egui::Ui, state: &mut GuiState) {
|
||
ui.vertical(|ui| {
|
||
// Section header
|
||
ui.heading("CPU Registers");
|
||
|
||
// Index register selector
|
||
ui.horizontal(|ui| {
|
||
ui.label("Index Register:");
|
||
ui.add(
|
||
egui::DragValue::new(&mut state.register_state.selected_register)
|
||
.clamp_range(0..=15),
|
||
);
|
||
});
|
||
|
||
ui.separator();
|
||
|
||
// Register display grid
|
||
egui::Grid::new("register_grid")
|
||
.striped(true)
|
||
.show(ui, |ui| {
|
||
// Header row
|
||
ui.label("Register");
|
||
ui.label("Value (Hex)");
|
||
ui.label("Value (Dec)");
|
||
ui.end_row();
|
||
|
||
// Core CPU registers
|
||
ui.label("Accumulator");
|
||
ui.label(format!("{:X}", state.register_state.accumulator));
|
||
ui.label(format!("{}", state.register_state.accumulator));
|
||
ui.end_row();
|
||
|
||
// System flags
|
||
ui.label("Carry Flag");
|
||
ui.label(if state.register_state.carry_flag {
|
||
"1"
|
||
} else {
|
||
"0"
|
||
});
|
||
ui.label(if state.register_state.carry_flag {
|
||
"Set"
|
||
} else {
|
||
"Clear"
|
||
});
|
||
ui.end_row();
|
||
|
||
// Program execution state
|
||
ui.label("Program Counter");
|
||
ui.label(format!("{:03X}", state.register_state.program_counter));
|
||
ui.label(format!("{}", state.register_state.program_counter));
|
||
ui.end_row();
|
||
|
||
// Selected index register (highlighted)
|
||
let selected = state.register_state.selected_register;
|
||
ui.label(format!("Index R{}", selected));
|
||
ui.label(format!(
|
||
"{:X}",
|
||
state.register_state.index_registers[selected]
|
||
));
|
||
ui.label(format!(
|
||
"{}",
|
||
state.register_state.index_registers[selected]
|
||
));
|
||
ui.end_row();
|
||
|
||
// Stack management
|
||
ui.label("Stack Pointer");
|
||
ui.label(format!("{:X}", state.register_state.stack_pointer));
|
||
ui.label(format!("{}", state.register_state.stack_pointer));
|
||
ui.end_row();
|
||
});
|
||
});
|
||
|
||
ui.separator();
|
||
}
|
||
}
|
||
|
||
/// ROM viewer component for displaying ROM contents
|
||
///
|
||
/// The ROM viewer provides comprehensive ROM inspection capabilities,
|
||
/// allowing users to examine program memory contents with flexible
|
||
/// display options and intuitive navigation.
|
||
///
|
||
/// ## Features
|
||
///
|
||
/// - **Program Memory Display**: View ROM contents from Intel 4001 chips
|
||
/// - **Flexible Formatting**: Toggle between hexadecimal and decimal display
|
||
/// - **Address Navigation**: Jump to specific memory addresses
|
||
/// - **Scrollable Interface**: Navigate through memory contents efficiently
|
||
/// - **Real-time Updates**: Live ROM content display
|
||
///
|
||
/// ## Display Format
|
||
///
|
||
/// The ROM viewer shows data in a structured grid:
|
||
/// - **Address Column**: Memory addresses in hexadecimal format
|
||
/// - **Data Columns**: 16 bytes per row for easy viewing
|
||
/// - **Value Display**: Configurable hex or decimal representation
|
||
///
|
||
/// ## Memory Organization
|
||
///
|
||
/// The Intel 4001 ROM has the following structure:
|
||
/// - **256 Bytes**: Total ROM capacity per chip
|
||
/// - **8-bit Values**: Each memory location stores a full byte
|
||
pub struct RomViewer {
|
||
/// Number of bytes to display per row
|
||
bytes_per_row: usize,
|
||
}
|
||
|
||
impl RomViewer {
|
||
/// Create a new ROM viewer with default configuration
|
||
///
|
||
/// Initializes the viewer with standard layout optimized for the Intel 4001 ROM structure.
|
||
///
|
||
/// # Returns
|
||
/// A new `RomViewer` instance ready for ROM content display
|
||
pub fn new() -> Self {
|
||
Self { bytes_per_row: 16 }
|
||
}
|
||
|
||
/// Render the ROM viewer interface
|
||
///
|
||
/// Creates a comprehensive ROM inspection interface with address navigation,
|
||
/// display options, and a scrollable memory contents grid.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Mutable reference to GUI state containing ROM data
|
||
///
|
||
/// # Layout Structure
|
||
/// ```text
|
||
/// ┌─────────────────────────────────────────────────┐
|
||
/// │ ROM Viewer (Chip: ROM_4001_1) [─] [□] │
|
||
/// │ □ Hex View │
|
||
/// ├─────────────────────────────────────────────────┤
|
||
/// │ Address 00 01 02 03 04 05 06 07 08 09 0A 0B... │
|
||
/// │ 0x000 D4 C3 B2 A1 00 11 22 33 44 55 66 77... │
|
||
/// │ 0x010 88 99 AA BB CC DD EE FF 00 11 22 33... │
|
||
/// │ ... │
|
||
/// └─────────────────────────────────────────────────┘
|
||
/// ```
|
||
pub fn render(&self, ui: &mut egui::Ui, state: &mut GuiState) {
|
||
ui.vertical(|ui| {
|
||
// Section header with chip name
|
||
let header = if state.rom_state.chip_name.is_empty() {
|
||
"ROM Viewer".to_string()
|
||
} else {
|
||
format!("ROM Viewer ({})", state.rom_state.chip_name)
|
||
};
|
||
ui.heading(header);
|
||
|
||
// Control bar with display options
|
||
ui.horizontal(|ui| {
|
||
ui.checkbox(&mut state.rom_state.show_hex, "Hex View");
|
||
});
|
||
|
||
ui.separator();
|
||
|
||
// ROM contents in scrollable area
|
||
egui::ScrollArea::vertical()
|
||
.max_height(200.0)
|
||
.show(ui, |ui| {
|
||
egui::Grid::new("rom_grid").striped(true).show(ui, |ui| {
|
||
// Header row with column labels
|
||
ui.label("Addr");
|
||
for i in 0..self.bytes_per_row {
|
||
ui.label(format!("{:02X}", i));
|
||
}
|
||
ui.end_row();
|
||
|
||
// ROM contents rows
|
||
let total_rows = (state.rom_state.rom_contents.len() + self.bytes_per_row
|
||
- 1)
|
||
/ self.bytes_per_row;
|
||
for row in 0..total_rows {
|
||
let addr = row * self.bytes_per_row;
|
||
|
||
// Address column
|
||
ui.label(format!("{:03X}", addr));
|
||
|
||
// Data columns
|
||
for col in 0..self.bytes_per_row {
|
||
let idx = addr + col;
|
||
if idx < state.rom_state.rom_contents.len() {
|
||
let value = state.rom_state.rom_contents[idx];
|
||
if state.rom_state.show_hex {
|
||
ui.label(format!("{:02X}", value));
|
||
} else {
|
||
ui.label(format!("{:3}", value));
|
||
}
|
||
} else {
|
||
ui.label("--");
|
||
}
|
||
}
|
||
ui.end_row();
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
ui.separator();
|
||
}
|
||
}
|
||
|
||
/// ROM loader component for file dialog integration and program management
|
||
///
|
||
/// The ROM loader handles program file selection, loading, and management.
|
||
/// It provides the interface for users to load Intel 4001 ROM files and
|
||
/// binary programs into the emulator system.
|
||
///
|
||
/// ## Features
|
||
///
|
||
/// - **File Dialog Integration**: Native file browser for program selection
|
||
/// - **Program Validation**: Basic file format and size validation
|
||
/// - **Load Feedback**: Visual confirmation of successful loads
|
||
/// - **Error Handling**: Clear error messages for failed operations
|
||
/// - **Future Extensions**: Support for multiple ROM chips and formats
|
||
///
|
||
/// ## Supported Formats
|
||
///
|
||
/// - **Binary Files**: Raw binary program data (.bin)
|
||
/// - **Intel 4001 Format**: MCS-4 ROM file format (planned)
|
||
/// - **Configuration Files**: System configuration integration (planned)
|
||
///
|
||
/// ## Integration Notes
|
||
///
|
||
/// Currently shows placeholder for file dialog implementation.
|
||
/// Production version should integrate with native file dialogs using:
|
||
/// - **rfd** crate for cross-platform file dialogs
|
||
/// - **async** operations for non-blocking file I/O
|
||
/// - **Progress feedback** for large file operations
|
||
pub struct RomLoader {
|
||
/// Whether to show the file selection dialog
|
||
show_file_dialog: bool,
|
||
/// Currently selected file path (if any)
|
||
selected_file: Option<String>,
|
||
}
|
||
|
||
impl RomLoader {
|
||
/// Create a new ROM loader with default state
|
||
///
|
||
/// Initializes the loader with no file selected and dialog closed.
|
||
/// Ready to handle user file selection requests.
|
||
///
|
||
/// # Returns
|
||
/// A new `RomLoader` instance ready for file operations
|
||
pub fn new() -> Self {
|
||
Self {
|
||
show_file_dialog: false,
|
||
selected_file: None,
|
||
}
|
||
}
|
||
|
||
/// Render the ROM loader interface
|
||
///
|
||
/// Creates the file management interface with load button and
|
||
/// file selection dialog integration.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `_state` - GUI state (currently unused but reserved for future integration)
|
||
///
|
||
/// # Layout Structure
|
||
/// ```text
|
||
/// ┌─────────────────────────────────────────────────┐
|
||
/// │ ROM Management [─] [□]│
|
||
/// │ [Load ROM File...] │
|
||
/// │ Selected: /path/to/program.bin │
|
||
/// │ │
|
||
/// │ [File Dialog Placeholder] │
|
||
/// └─────────────────────────────────────────────────┘
|
||
/// ```
|
||
pub fn render(&mut self, ui: &mut egui::Ui, _state: &mut GuiState) {
|
||
ui.vertical(|ui| {
|
||
ui.heading("ROM Management");
|
||
|
||
// Load button to trigger file selection
|
||
if ui.button("Load ROM File...").clicked() {
|
||
self.show_file_dialog = true;
|
||
}
|
||
|
||
// Display currently selected file
|
||
if let Some(ref file) = self.selected_file {
|
||
ui.label(format!("Selected: {}", file));
|
||
}
|
||
|
||
// File dialog implementation (placeholder)
|
||
if self.show_file_dialog {
|
||
// Placeholder for file dialog
|
||
ui.label("File dialog would open here");
|
||
if ui.button("Cancel").clicked() {
|
||
self.show_file_dialog = false;
|
||
}
|
||
}
|
||
});
|
||
|
||
ui.separator();
|
||
}
|
||
}
|
||
|
||
/// Status bar component for system status display and health monitoring
|
||
///
|
||
/// The status bar provides comprehensive system health information,
|
||
/// component status indicators, and real-time performance metrics.
|
||
/// It serves as the primary location for system state overview.
|
||
///
|
||
/// ## Features
|
||
///
|
||
/// - **System Status**: Running/stopped state with visual indicators
|
||
/// - **Performance Metrics**: Cycle count and execution speed
|
||
/// - **Component Health**: Individual component status monitoring
|
||
/// - **Error Display**: Real-time error message display
|
||
/// - **System Information**: CPU speed, component count, and configuration
|
||
///
|
||
/// ## Visual Design
|
||
///
|
||
/// - **Color Coding**: Green for healthy, red for errors/stopped
|
||
/// - **Layout Organization**: Status on left, errors on right
|
||
/// - **Real-time Updates**: Live status changes during operation
|
||
/// - **Compact Display**: Information-dense but readable layout
|
||
///
|
||
/// ## Component Status Indicators
|
||
///
|
||
/// Monitors the health of all major system components:
|
||
/// - **CPU**: Intel 4004 execution status
|
||
/// - **RAM**: Intel 4002 memory operations
|
||
/// - **ROM**: Intel 4001 program storage
|
||
/// - **CLK**: System clock generation
|
||
pub struct StatusBar;
|
||
|
||
impl StatusBar {
|
||
/// Create a new status bar component
|
||
///
|
||
/// Initializes a stateless status bar ready to display
|
||
/// current system health and performance information.
|
||
///
|
||
/// # Returns
|
||
/// A new `StatusBar` instance
|
||
pub fn new() -> Self {
|
||
Self
|
||
}
|
||
|
||
/// Render the status bar interface
|
||
///
|
||
/// Creates a comprehensive status display with system information,
|
||
/// component health indicators, and error reporting.
|
||
///
|
||
/// # Arguments
|
||
/// * `ui` - egui UI context for rendering
|
||
/// * `state` - Immutable reference to GUI state for status information
|
||
///
|
||
/// # Layout Structure
|
||
/// ```text
|
||
/// ┌─────────────────────────────────────────────────────────────┐
|
||
/// │ Status: ● Running │ Cycles: 12345 │ CPU: 0.7 MHz │
|
||
/// │ Components: 4 │ CPU RAM ROM CLK │ Error: Connection │
|
||
/// └─────────────────────────────────────────────────────────────┘
|
||
/// ```
|
||
pub fn render(&self, ui: &mut egui::Ui, state: &GuiState) {
|
||
ui.separator();
|
||
|
||
ui.horizontal(|ui| {
|
||
// Left side: Status information and metrics
|
||
ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
|
||
// System running status
|
||
ui.label("Status:");
|
||
if state.system_running {
|
||
ui.colored_label(egui::Color32::GREEN, "● Running");
|
||
} else {
|
||
ui.colored_label(egui::Color32::RED, "● Stopped");
|
||
}
|
||
|
||
ui.separator();
|
||
|
||
// Performance metrics
|
||
ui.label(format!("Cycles: {}", state.cycle_count));
|
||
|
||
ui.separator();
|
||
|
||
// System information
|
||
if let Some(ref info) = state.system_info {
|
||
ui.label(format!("CPU: {:.1} MHz", info.cpu_speed / 1_000_000.0));
|
||
ui.label(format!("Components: {}", info.component_count));
|
||
}
|
||
|
||
ui.separator();
|
||
|
||
// Component status indicators
|
||
ui.label("Components:");
|
||
ui.colored_label(
|
||
if state.component_states.cpu_running {
|
||
egui::Color32::GREEN
|
||
} else {
|
||
egui::Color32::RED
|
||
},
|
||
"CPU",
|
||
);
|
||
ui.colored_label(
|
||
if state.component_states.ram_running {
|
||
egui::Color32::GREEN
|
||
} else {
|
||
egui::Color32::RED
|
||
},
|
||
"RAM",
|
||
);
|
||
ui.colored_label(
|
||
if state.component_states.rom_running {
|
||
egui::Color32::GREEN
|
||
} else {
|
||
egui::Color32::RED
|
||
},
|
||
"ROM",
|
||
);
|
||
ui.colored_label(
|
||
if state.component_states.clock_running {
|
||
egui::Color32::GREEN
|
||
} else {
|
||
egui::Color32::RED
|
||
},
|
||
"CLK",
|
||
);
|
||
});
|
||
|
||
// Right side: Error messages
|
||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||
if let Some(ref error) = state.last_error {
|
||
ui.colored_label(egui::Color32::RED, format!("Error: {}", error));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|