pub mod screen;
mod utils;
use crate::tui::screen::help_screen::HelpScreen;
use crate::tui::screen::selection_screen::SelectionScreen;
use crossterm::{
event::{self, Event, KeyCode, KeyEventKind},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{
Terminal,
backend::{Backend, CrosstermBackend},
widgets::ListState,
};
use screen::Screen;
use std::{io, rc::Rc};
use crate::models::ProblemSummary;
#[derive(Default)]
pub enum Tab {
#[default]
Selection,
Help,
}
pub struct App {
pub should_quit: bool,
pub problems: Rc<[ProblemSummary]>,
pub tab: Tab,
pub selection_screen: SelectionScreen,
pub help_screen: HelpScreen,
pub selected_problem: Option<String>,
}
pub enum Action {
Quit,
Select(String),
}
impl App {
pub fn new(problems: Rc<[ProblemSummary]>) -> Self {
let mut list_state = ListState::default();
if !problems.is_empty() {
list_state.select(Some(0)); }
Self {
should_quit: false,
selection_screen: SelectionScreen::new(Rc::clone(&problems)),
problems,
tab: Tab::default(),
selected_problem: None,
help_screen: HelpScreen::new(),
}
}
pub fn switch(&mut self) {
self.tab = match self.tab {
Tab::Help => Tab::Selection,
Tab::Selection => Tab::Help,
}
}
}
pub async fn run_tui(
problems: Rc<[ProblemSummary]>,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let mut app = App::new(problems);
let res = run_app(&mut terminal, &mut app).await;
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
if let Err(err) = res {
eprintln!("Error running TUI: {:?}", err);
}
Ok(app.selected_problem)
}
async fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> io::Result<()> {
loop {
let screen: &mut dyn Screen = match app.tab {
Tab::Selection => &mut app.selection_screen,
Tab::Help => &mut app.help_screen,
};
let _ = terminal.draw(|f| screen.render(f));
if event::poll(std::time::Duration::from_millis(50))? {
let event = event::read()?;
if let Event::Key(key) = event
&& key.kind == KeyEventKind::Press
{
match key.code {
KeyCode::Tab => app.switch(),
KeyCode::Char('?') => app.tab = Tab::Help,
_ => {
if let Some(action) = screen.event_loop(&key) {
match action {
Action::Quit => {
app.should_quit = true;
}
Action::Select(problem) => {
app.selected_problem = Some(problem);
app.should_quit = true;
}
}
}
}
}
}
}
if app.should_quit {
return Ok(());
}
}
}