//! # Console Interface Module //! //! Interactive terminal-based user interface for the Intel MCS-4 emulator. //! Provides real-time system monitoring, debugging capabilities, and user controls. //! //! ## Features //! - Real-time RAM and register display //! - Interactive command interface //! - System state monitoring //! - Configurable display options //! - Graceful interrupt handling use crossterm::{ event::{self, Event, KeyCode}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use ratatui::{ backend::CrosstermBackend, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, Paragraph, Wrap}, Frame, Terminal, }; use serde::{Deserialize, Serialize}; use std::io; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; use crate::system_config::ConfigurableSystem; /// Console configuration structure #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConsoleConfig { pub enabled: bool, pub refresh_rate_ms: u64, pub show_ram: bool, pub show_registers: bool, pub show_system_info: bool, pub ram_banks_per_row: usize, pub max_ram_rows: usize, } impl Default for ConsoleConfig { fn default() -> Self { Self { enabled: true, refresh_rate_ms: 100, show_ram: true, show_registers: true, show_system_info: true, ram_banks_per_row: 4, max_ram_rows: 5, } } } /// Console UI application state pub struct ConsoleApp { system: Arc>, config: ConsoleConfig, running: bool, command_buffer: String, show_help: bool, selected_pane: usize, } impl ConsoleApp { pub fn new(system: Arc>, config: ConsoleConfig) -> Self { Self { system, config, running: false, command_buffer: String::new(), show_help: false, selected_pane: 0, } } pub fn run(&mut self) -> Result<(), Box> { // Setup terminal enable_raw_mode().map_err(|e| format!("Failed to enable raw mode: {}", e))?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen) .map_err(|e| format!("Failed to enter alternate screen: {}", e))?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; self.running = true; let mut last_draw = Instant::now(); // Main event loop while self.running { let now = Instant::now(); // Handle input if let Ok(true) = event::poll(Duration::from_millis(10)) { if let Ok(Event::Key(key)) = event::read() { self.handle_key_event(key.code); } } // Update display at regular intervals if now.duration_since(last_draw) >= Duration::from_millis(self.config.refresh_rate_ms) { if let Err(e) = terminal.draw(|f| self.draw_ui(f)) { eprintln!("DEBUG: Failed to draw UI: {}", e); break; } last_draw = now; } // Check if system should still be running if let Ok(system) = self.system.lock() { if !system.is_running() && !self.show_help { // System stopped, show final state briefly if let Err(e) = terminal.draw(|f| self.draw_ui(f)) { eprintln!("DEBUG: Failed to draw final UI: {}", e); } thread::sleep(Duration::from_millis(500)); break; } } // Small delay to prevent busy waiting thread::sleep(Duration::from_millis(1)); } // Restore terminal disable_raw_mode().map_err(|e| format!("Failed to disable raw mode: {}", e))?; execute!(terminal.backend_mut(), LeaveAlternateScreen) .map_err(|e| format!("Failed to leave alternate screen: {}", e))?; terminal .show_cursor() .map_err(|e| format!("Failed to show cursor: {}", e))?; Ok(()) } fn handle_key_event(&mut self, key: KeyCode) { match key { KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => { println!("DEBUG: Quit key pressed, stopping console"); self.running = false; if let Ok(mut system) = self.system.lock() { system.stop(); } } KeyCode::Char('h') | KeyCode::Char('H') => { println!("DEBUG: Help key pressed"); self.show_help = !self.show_help; } KeyCode::Char('r') | KeyCode::Char('R') => { println!("DEBUG: Run key pressed"); if let Ok(mut system) = self.system.lock() { if system.is_running() { system.stop(); } else { system.run(); } } } KeyCode::Char('s') | KeyCode::Char('S') => { println!("DEBUG: Stop key pressed"); if let Ok(mut system) = self.system.lock() { system.stop(); } } KeyCode::Char(' ') => { println!("DEBUG: Space key pressed - single step not implemented"); // Single step (if supported) // This would require adding step functionality to the system } KeyCode::Tab => { println!("DEBUG: Tab key pressed - switching panes"); self.selected_pane = (self.selected_pane + 1) % 3; } KeyCode::Backspace => { println!("DEBUG: Backspace key pressed"); self.command_buffer.pop(); } KeyCode::Enter => { println!( "DEBUG: Enter key pressed - executing command: '{}'", self.command_buffer ); self.execute_command(); self.command_buffer.clear(); } KeyCode::Char(c) => { println!("DEBUG: Character key pressed: '{}'", c); if c.is_ascii_alphabetic() || c.is_ascii_digit() { self.command_buffer.push(c); } } _ => { println!("DEBUG: Unhandled key pressed: {:?}", key); } } } fn execute_command(&mut self) { let cmd = self.command_buffer.trim().to_lowercase(); println!("DEBUG: Executing command: '{}'", cmd); match cmd.as_str() { "quit" | "exit" | "q" => { println!("DEBUG: Executing quit command"); self.running = false; if let Ok(mut system) = self.system.lock() { system.stop(); } } "run" | "r" => { println!("DEBUG: Executing run command"); if let Ok(mut system) = self.system.lock() { system.run(); } } "stop" | "s" => { println!("DEBUG: Executing stop command"); if let Ok(mut system) = self.system.lock() { system.stop(); } } "help" | "h" => { println!("DEBUG: Toggling help display"); self.show_help = !self.show_help; } "reset" => { println!("DEBUG: Executing reset command"); if let Ok(mut system) = self.system.lock() { system.stop(); // Reset would need to be implemented in the system } } "status" => { println!("DEBUG: Executing status command"); if let Ok(system) = self.system.lock() { let info = system.get_system_info(); println!("System: {} - {}", info.name, info.description); println!( "Components: {}, Running: {}", info.component_count, system.is_running() ); } } "ram" => { println!("DEBUG: Executing RAM display command"); if let Ok(_system) = self.system.lock() { println!("RAM display requested - would show RAM contents here"); } } "" => { // Empty command - do nothing } _ => { println!("DEBUG: Unknown command: '{}'", cmd); println!("Available commands: quit, run, stop, help, reset, status, ram"); } } } fn draw_ui(&self, f: &mut Frame) { let size = f.size(); if self.show_help { self.draw_help_screen(f); return; } // Create main layout with proper constraints let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(4), // Title bar Constraint::Min(8), // Main content Constraint::Length(3), // Command bar ]) .split(size); // Title bar let title_text = vec![ Line::from(vec![Span::styled( "Intel MCS-4 Emulator Console", Style::default() .fg(Color::Cyan) .add_modifier(Modifier::BOLD), )]), Line::from(vec![ Span::raw("Commands: "), Span::styled("q/Q", Style::default().fg(Color::Yellow)), Span::raw("=quit, "), Span::styled("r/R", Style::default().fg(Color::Yellow)), Span::raw("=run, "), Span::styled("s/S", Style::default().fg(Color::Yellow)), Span::raw("=stop, "), Span::styled("h/H", Style::default().fg(Color::Yellow)), Span::raw("=help"), ]), ]; let title = Paragraph::new(title_text) .block(Block::default().borders(Borders::ALL).title("Status")) .wrap(Wrap { trim: true }); f.render_widget(title, chunks[0]); // Main content area let content_chunks = Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Percentage(50), // Left pane Constraint::Percentage(50), // Right pane ]) .split(chunks[1]); // Left pane - System info and registers self.draw_system_info(f, content_chunks[0]); // Right pane - RAM contents self.draw_ram_contents(f, content_chunks[1]); // Command bar let command_text = if self.command_buffer.is_empty() { "Enter command (type 'h' for help)..." } else { &self.command_buffer }; let command_bar = Paragraph::new(command_text) .style(Style::default().fg(Color::White)) .block(Block::default().borders(Borders::ALL).title("Command")); f.render_widget(command_bar, chunks[2]); } fn draw_help_screen(&self, f: &mut Frame) { let size = f.size(); let help_text = vec![ Line::from(vec![Span::styled( "Intel MCS-4 Emulator Console Help", Style::default() .fg(Color::Cyan) .add_modifier(Modifier::BOLD), )]), Line::from(""), Line::from(vec![Span::styled( "Commands:", Style::default().add_modifier(Modifier::BOLD), )]), Line::from(vec![ Span::styled(" q, quit, exit", Style::default().fg(Color::Yellow)), Span::raw(" - Exit emulator"), ]), Line::from(vec![ Span::styled(" r, run", Style::default().fg(Color::Yellow)), Span::raw(" - Start/stop system execution"), ]), Line::from(vec![ Span::styled(" s, stop", Style::default().fg(Color::Yellow)), Span::raw(" - Stop system execution"), ]), Line::from(vec![ Span::styled(" h, help", Style::default().fg(Color::Yellow)), Span::raw(" - Show/hide this help"), ]), Line::from(vec![ Span::styled(" reset", Style::default().fg(Color::Yellow)), Span::raw(" - Reset system"), ]), Line::from(""), Line::from(vec![Span::styled( "Navigation:", Style::default().add_modifier(Modifier::BOLD), )]), Line::from(vec![ Span::styled(" Tab", Style::default().fg(Color::Yellow)), Span::raw(" - Switch between panes"), ]), Line::from(vec![ Span::styled(" Enter", Style::default().fg(Color::Yellow)), Span::raw(" - Execute command"), ]), Line::from(vec![ Span::styled(" Backspace", Style::default().fg(Color::Yellow)), Span::raw(" - Delete character"), ]), Line::from(""), Line::from(vec![Span::raw("Press any key to return to main view...")]), ]; let help = Paragraph::new(help_text) .style(Style::default().fg(Color::White)) .wrap(Wrap { trim: true }) .block(Block::default().borders(Borders::ALL).title("Help")); f.render_widget(help, size); } fn draw_system_info(&self, f: &mut Frame, area: Rect) { let info_chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(8), // System info Constraint::Min(5), // Registers ]) .split(area); // System information let system_info = match self.system.lock() { Ok(system) => match system.get_system_info() { info => vec![ Line::from(vec![Span::raw(format!("System: {}", info.name))]), Line::from(vec![Span::raw(format!( "Description: {}", info.description ))]), Line::from(vec![Span::raw(format!( "Components: {}", info.component_count ))]), Line::from(vec![Span::raw(format!("CPU Speed: {} Hz", info.cpu_speed))]), Line::from(vec![Span::raw(format!( "ROM Size: {} bytes", info.rom_size ))]), Line::from(vec![Span::raw(format!( "RAM Size: {} nibbles", info.ram_size ))]), Line::from(vec![Span::raw(format!("Running: {}", system.is_running()))]), ], }, Err(_) => { vec![Line::from(vec![Span::raw( "System information unavailable", )])] } }; let system_widget = Paragraph::new(system_info) .block( Block::default() .borders(Borders::ALL) .title("System Information"), ) .wrap(Wrap { trim: true }); f.render_widget(system_widget, info_chunks[0]); // CPU register display (enhanced with execution status) let register_info = vec![ Line::from(vec![Span::raw("CPU Registers:")]), Line::from(vec![Span::raw("Status: Running (check console output)")]), Line::from(vec![Span::raw("PC: 0x000 (see DEBUG output)")]), Line::from(vec![Span::raw("ACC: 0x0 (see DEBUG output)")]), Line::from(vec![Span::raw( "Instructions: Executing (see DEBUG output)", )]), ]; let register_widget = Paragraph::new(register_info) .block( Block::default() .borders(Borders::ALL) .title("CPU Registers"), ) .wrap(Wrap { trim: true }); f.render_widget(register_widget, info_chunks[1]); } fn draw_ram_contents(&self, f: &mut Frame, area: Rect) { let mut ram_info = vec![Line::from(vec![Span::raw("RAM Contents:")])]; // Try to get actual RAM data from the system match self.system.lock() { Ok(_system) => { // This is a simplified version - in a real implementation, // we would need to access the actual RAM components // For now, show a more informative placeholder ram_info.push(Line::from(vec![Span::raw("Reading RAM contents...")])); // Show some sample memory ranges for bank in 0..4 { let mut bank_data = format!("Bank {}: [", bank); for i in 0..20 { if i > 0 && i % 4 == 0 { bank_data.push(' '); } bank_data.push_str("00"); } bank_data.push(']'); ram_info.push(Line::from(vec![Span::raw(bank_data)])); } } Err(_) => { ram_info.push(Line::from(vec![Span::raw("Unable to access RAM data")])); } } let ram_widget = Paragraph::new(ram_info) .block(Block::default().borders(Borders::ALL).title("RAM Contents")) .wrap(Wrap { trim: true }); f.render_widget(ram_widget, area); } } /// Public interface for launching the console pub fn run_console( system: Arc>, config: ConsoleConfig, ) -> Result<(), Box> { let mut app = ConsoleApp::new(system, config); app.run() }