use std::io; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use log::error; use ratatui::{ DefaultTerminal, Frame, layout::{Constraint, Direction, Layout, Rect}, widgets::{StatefulWidget, TableState, Widget}, }; use crate::{ tui::{ instance::{InstanceSelect, InstanceSelectState}, mod_list::ModList, status::StatusBar, }, types::{ModdedInstance, RootConfig}, }; pub fn run(root_config: &mut RootConfig) -> anyhow::Result<()> { ratatui::run(|terminal| App::new(root_config).run(terminal))?; Ok(()) } #[derive(Debug)] struct App<'a> { root_config: &'a mut RootConfig, loaded_instance: Option, exit: bool, mod_list_state: TableState, selected_instance_state: InstanceSelectState, } impl<'a> App<'a> { fn new(root_config: &'a mut RootConfig) -> Self { Self { root_config, loaded_instance: None, exit: false, mod_list_state: TableState::default(), selected_instance_state: InstanceSelectState::new(), } } pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { terminal.clear()?; while !self.exit { terminal.draw(|frame| self.draw(frame))?; self.handle_events()?; } Ok(()) } fn draw(&mut self, frame: &mut Frame) { frame.render_widget(self, frame.area()); } fn handle_events(&mut self) -> io::Result<()> { match event::read()? { Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { self.handle_key_event(key_event) } _ => {} }; Ok(()) } fn handle_key_event(&mut self, key_event: KeyEvent) { match key_event.code { KeyCode::Esc | KeyCode::Char('q') => self.exit(), KeyCode::Up | KeyCode::Char('k') => { self.mod_list_state.select_previous(); } KeyCode::Down | KeyCode::Char('j') => { self.mod_list_state.select_next(); } KeyCode::Right | KeyCode::Char('l') => { self.selected_instance_state.next_instance(self.root_config); self.load_instance(); } KeyCode::Left | KeyCode::Char('h') => { self.selected_instance_state.prev_instance(self.root_config); self.load_instance(); } _ => {} } } fn exit(&mut self) { self.exit = true; } fn load_instance(&mut self) { let Some(selected) = self.selected_instance_state.instance() else { return; }; match self.root_config.load_instance_by_id(selected) { Ok(instance) => { self.loaded_instance = Some(instance); } Err(err) => { error!("Failed to load instance: {err}"); self.exit(); } } } } impl<'a> Widget for &mut App<'a> { fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer) where Self: Sized, { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(3), Constraint::Min(1), Constraint::Length(1), ]) .split(area); InstanceSelect {}.render(chunks[0], buf, &mut self.selected_instance_state); ModList::new(self.root_config).render(chunks[1], buf, &mut self.mod_list_state); StatusBar.render(chunks[2], buf); } }