//! Phase-0-Spike: 80×25-Bildschirmpuffer, 16-Farben-Palette, Unicode, //! Tastatur- und Mausereignisse. //! //! Start: `cargo run -p tb-ui --features terminal --example spike -- --log events.txt` — Beenden mit Esc. //! Getippte Zeichen erscheinen im Eingabebereich; Sondertasten und //! Mausereignisse werden in der Statuszeile angezeigt. use crossterm::event::{self, Event, KeyCode, KeyEventKind}; use std::io::{stdout, Write}; use std::time::Duration; use tb_ui::screen::{ScreenWidget, TextScreen}; fn testbild(s: &mut TextScreen) { s.set_color(15, 1); s.print(&format!( "{:^80}", "Terminal Basic — Phase-0-Spike (Esc beendet)" )); // Farbraster: alle Vordergrundfarben auf allen Hintergründen for bg in 0u8..8 { s.set_color(15, 0); s.locate(3 + bg as usize, 3).unwrap(); s.print(&format!("HG {bg} ")); for fg in 0u8..16 { s.set_color(fg, bg); s.print(&format!(" {fg:2}")); } } // Unicode statt CP437: Rahmen, Umlaute, Symbole s.set_color(14, 0); s.locate(12, 3).unwrap(); s.print("┌─ Unicode ────────────────────────────┐"); s.locate(13, 3).unwrap(); s.print("│ Äpfel, Öl, Übermaß, ß — ☃ ♥ ♦ ♣ ♠ π λ │"); s.locate(14, 3).unwrap(); s.print("└──────────────────────────────────────┘"); s.set_color(7, 0); s.locate(16, 3).unwrap(); s.print("Eingabe (Zeichen erscheinen hier):"); s.locate(17, 3).unwrap(); } fn status(s: &mut TextScreen, text: &str) { let (r, c) = (s.csrlin(), s.pos()); let (row, width) = (s.rows(), s.cols()); s.set_color(0, 7); s.locate(row, 1).unwrap(); s.print(&format!("{text: anyhow::Result<()> { let args: Vec<_> = std::env::args_os().skip(1).collect(); anyhow::ensure!( args.is_empty() || (args.len() == 2 && args[0] == "--log"), "Aufruf: spike [--log neue-datei]" ); let mut log = if args.len() == 2 { Some( std::fs::OpenOptions::new() .write(true) .create_new(true) .open(&args[1])?, ) } else { None }; let _guard = tb_ui::terminal::TerminalGuard::enter(stdout())?; let mut terminal = ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(stdout()))?; let mut sequence = 0u64; let (tw, th) = crossterm::terminal::size()?; let mut screen = TextScreen::with_size(tw as usize, th as usize); testbild(&mut screen); status(&mut screen, "Bereit. Tasten/Maus testen, Esc beendet."); loop { terminal.draw(|f| f.render_widget(ScreenWidget(&screen), f.area()))?; if !event::poll(Duration::from_millis(100))? { continue; } let received = event::read()?; sequence += 1; if let Some(log) = &mut log { writeln!(log, "{sequence}: {received:?}")?; } match received { Event::Key(k) if k.kind != KeyEventKind::Release => match k.code { KeyCode::Esc => break, KeyCode::Char(c) => { screen.print(&c.to_string()); status( &mut screen, &format!("Taste: {:?} Modifier: {:?}", k.code, k.modifiers), ); } KeyCode::Enter => screen.print("\n"), other => status( &mut screen, &format!("Sondertaste: {other:?} Modifier: {:?}", k.modifiers), ), }, Event::Mouse(m) => { status( &mut screen, &format!( "Maus: {:?} bei Spalte {}, Zeile {}", m.kind, m.column + 1, m.row + 1 ), ); } Event::Resize(w, h) => { screen.resize(w as usize, h as usize); status(&mut screen, &format!("Resize: {w}x{h}")); } _ => {} } } Ok(()) }