use std::{collections::VecDeque, sync::mpsc}; use egui::{Align, CentralPanel, FontFamily, Label, RichText, ScrollArea, Ui, ViewportBuilder}; use crate::emulator::{EmulatorClient, EmulatorCommand, SimId}; use super::AppWindow; const SCROLLBACK: usize = 1000; pub struct TerminalWindow { sim_id: SimId, receiver: mpsc::Receiver, lines: VecDeque, } impl TerminalWindow { pub fn new(sim_id: SimId, client: &EmulatorClient) -> Self { let (sender, receiver) = mpsc::channel(); client.send_command(EmulatorCommand::WatchStdout(sim_id, sender)); let mut lines = VecDeque::new(); lines.push_back(String::new()); Self { sim_id, receiver, lines, } } } impl AppWindow for TerminalWindow { fn sim_id(&self) -> Option { Some(self.sim_id) } fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title("Terminal") .with_inner_size((640.0, 480.0)) } fn show(&mut self, ui: &mut Ui) { if let Ok(text) = self.receiver.try_recv() { let mut rest = text.as_str(); while let Some(index) = rest.find('\n') { let (line, lines) = rest.split_at(index); let current = self.lines.back_mut().unwrap(); current.push_str(line); self.lines.push_back(String::new()); if self.lines.len() > SCROLLBACK { self.lines.pop_front(); } rest = &lines[1..]; } self.lines.back_mut().unwrap().push_str(rest); } CentralPanel::default().show_inside(ui, |ui| { ScrollArea::vertical() .stick_to_bottom(true) .auto_shrink([false, false]) .animated(false) .show(ui, |ui| { for line in &self.lines { let label = Label::new(RichText::new(line).family(FontFamily::Monospace)) .halign(Align::LEFT) .wrap(); ui.add(label); } }); }); } }