evolution_crypting/src/tui.rs
Gregory Bednov ff049620d5 новый файл: .gitignore
новый файл:    Cargo.lock
	новый файл:    Cargo.toml
	новый файл:    english.chrs
	новый файл:    russian.chrs
	новый файл:    src/alphabet.rs
	новый файл:    src/app.rs
	новый файл:    src/ciphertext.rs
	новый файл:    src/cli.rs
	новый файл:    src/decoder.rs
	новый файл:    src/main.rs
	новый файл:    src/storage.rs
	новый файл:    src/tui.rs
2026-03-30 01:41:24 +03:00

168 lines
5.1 KiB
Rust

use crate::ciphertext::Ciphertext;
use crate::decoder::DecoderState;
use crate::storage::AlphabetFile;
use crossterm::{
event::{self, Event, KeyCode, KeyEventKind},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use rand_chacha::ChaCha20Rng;
use ratatui::{
prelude::*,
widgets::{Block, Borders, List, ListItem, Paragraph},
};
use std::io;
use std::path::PathBuf;
use std::time::Duration;
struct TuiGuard;
impl Drop for TuiGuard {
fn drop(&mut self) {
let _ = disable_raw_mode();
let _ = execute!(io::stdout(), LeaveAlternateScreen);
}
}
fn get_visible_symbols(
terminal: &Terminal<CrosstermBackend<std::io::Stdout>>,
) -> io::Result<usize> {
let size = terminal.size()?;
let inner_width = size.width.saturating_sub(2) as usize;
Ok(inner_width.max(1))
}
pub fn run() -> io::Result<()> {
todo!("call run_decoder_tui(alphabet_path, ciphertext_path) from CLI later")
}
pub fn run_decoder_tui(alphabet_path: PathBuf, ciphertext_path: PathBuf) -> io::Result<()> {
let alphabet_bytes = std::fs::read(&alphabet_path)?;
let bytes = std::fs::read(ciphertext_path)?;
let ciphertext = Ciphertext::from_bytes(&bytes).map_err(io::Error::other)?;
let rng_seed = [7u8; 32];
let mut state = DecoderState::new(alphabet_bytes, rng_seed, ciphertext.bits)?;
enable_raw_mode()?;
execute!(io::stdout(), EnterAlternateScreen)?;
let _guard = TuiGuard;
let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;
loop {
terminal.draw(|f| draw_ui(f, &state))?;
if event::poll(Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
if key.kind != KeyEventKind::Press {
continue;
}
let visible = get_visible_symbols(&terminal)?;
match key.code {
KeyCode::Char('q') => break,
KeyCode::Left | KeyCode::Char('h') => state.move_left(visible),
KeyCode::Right | KeyCode::Char('l') => state.move_right(visible),
KeyCode::Char('u') => {
let _ = state.undo_current();
}
KeyCode::Char('1') => {
let _ = state.apply_candidate(0);
}
KeyCode::Char('2') => {
let _ = state.apply_candidate(1);
}
KeyCode::Char('3') => {
let _ = state.apply_candidate(2);
}
KeyCode::Char('4') => {
let _ = state.apply_candidate(3);
}
KeyCode::Char('5') => {
let _ = state.apply_candidate(4);
}
KeyCode::Char('m') => {
state.set_ranking_mode(crate::decoder::RankingMode::LocalFirst);
}
KeyCode::Char('M') => {
state.set_ranking_mode(crate::decoder::RankingMode::FutureFirst);
}
_ => {}
}
}
}
}
Ok(())
}
fn draw_ui(f: &mut Frame, state: &DecoderState) {
let layout = Layout::vertical([
Constraint::Length(5),
Constraint::Min(10),
Constraint::Length(3),
])
.split(f.area());
draw_text_panel(f, layout[0], state);
draw_candidates_panel(f, layout[1], state);
draw_help_panel(f, layout[2]);
}
fn draw_text_panel(f: &mut Frame, area: Rect, state: &DecoderState) {
let cursor = state.cursor;
let rendered: String = state
.rendered_text
.chars()
.enumerate()
.flat_map(|(i, ch)| {
if i == cursor {
vec!['[', ch, ']']
} else {
vec![ch]
}
})
.collect();
let p = Paragraph::new(rendered)
.block(Block::default().title("Decoded text").borders(Borders::ALL));
f.render_widget(p, area);
}
fn draw_candidates_panel(f: &mut Frame, area: Rect, state: &DecoderState) {
let items = match state.current_candidates() {
Ok(cands) => cands
.into_iter()
.enumerate()
.map(|(i, c)| {
ListItem::new(format!(
"{}. {:?} local={} future={}",
i + 1,
c.symbol,
c.distance,
c.future_distance
))
})
.collect::<Vec<_>>(),
Err(err) => vec![ListItem::new(format!("error: {}", err))],
};
let list = List::new(items).block(
Block::default()
.title("Top-5 candidates")
.borders(Borders::ALL),
);
f.render_widget(list, area);
}
fn draw_help_panel(f: &mut Frame, area: Rect) {
let p = Paragraph::new("h/← left l/→ right 1..5 choose u undo q quit")
.block(Block::default().title("Keys").borders(Borders::ALL));
f.render_widget(p, area);
}