diff --git a/src/tui/app.rs b/src/tui/app.rs index 12ad08f..eda6689 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,16 +1,20 @@ use std::io; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; +use log::error; use ratatui::{ DefaultTerminal, Frame, layout::{Constraint, Direction, Layout, Rect}, - style::Style, - widgets::{Block, Borders, StatefulWidget, TableState, Widget}, + widgets::{StatefulWidget, TableState, Widget}, }; use crate::{ - tui::{instance::InstanceSelect, mod_list::ModList, status::StatusBar}, - types::RootConfig, + tui::{ + instance::{InstanceSelect, InstanceSelectState}, + mod_list::ModList, + status::StatusBar, + }, + types::{ModdedInstance, RootConfig}, }; pub fn run(root_config: &mut RootConfig) -> anyhow::Result<()> { @@ -22,24 +26,24 @@ pub fn run(root_config: &mut RootConfig) -> anyhow::Result<()> { struct App<'a> { root_config: &'a mut RootConfig, + loaded_instance: Option, + exit: bool, - active_modal: bool, mod_list_state: TableState, - selected_instance: Option, + selected_instance_state: InstanceSelectState, } impl<'a> App<'a> { fn new(root_config: &'a mut RootConfig) -> Self { - let mut state = TableState::default(); - state.select(Some(0)); // select first row by default - Self { root_config, - mod_list_state: state, - selected_instance: None, + + loaded_instance: None, + exit: false, - active_modal: false, + mod_list_state: TableState::default(), + selected_instance_state: InstanceSelectState::new(), } } @@ -76,10 +80,12 @@ impl<'a> App<'a> { self.mod_list_state.select_next(); } KeyCode::Right | KeyCode::Char('l') => { - self.next_instance(); + self.selected_instance_state.next_instance(self.root_config); + self.load_instance(); } KeyCode::Left | KeyCode::Char('h') => { - self.prev_instance(); + self.selected_instance_state.prev_instance(self.root_config); + self.load_instance(); } _ => {} } @@ -89,56 +95,20 @@ impl<'a> App<'a> { self.exit = true; } - fn next_instance(&mut self) { - let mut instances = self.root_config.instances(); - instances.sort(); - - if instances.is_empty() { - self.selected_instance = None; + fn load_instance(&mut self) { + let Some(selected) = self.selected_instance_state.instance() else { return; - } - - let next = match &self.selected_instance { - None => instances.first().cloned(), - Some(curr) => { - let idx = instances.iter().position(|x| x == curr); - match idx { - Some(i) => { - let next_index = (i + 1) % instances.len(); - instances.get(next_index).cloned() - } - None => instances.first().cloned(), - } - } }; - self.selected_instance = next; - } - - fn prev_instance(&mut self) { - let mut instances = self.root_config.instances(); - instances.sort(); - - if instances.is_empty() { - self.selected_instance = None; - return; - } - - let prev = match &self.selected_instance { - None => instances.last().cloned(), - Some(curr) => { - let idx = instances.iter().position(|x| x == curr); - match idx { - Some(i) => { - let prev_index = if i == 0 { instances.len() - 1 } else { i - 1 }; - instances.get(prev_index).cloned() - } - None => Some(instances[instances.len() - 1].clone()), - } + match self.root_config.load_instance_by_id(selected) { + Ok(instance) => { + self.loaded_instance = Some(instance); } - }; - - self.selected_instance = prev; + Err(err) => { + error!("Failed to load instance: {err}"); + self.exit(); + } + } } } @@ -152,14 +122,12 @@ impl<'a> Widget for &mut App<'a> { .constraints([ Constraint::Length(3), Constraint::Min(1), - Constraint::Length(1), // single line for keybindings + Constraint::Length(1), ]) .split(area); - InstanceSelect::new(self.selected_instance.clone()).render(chunks[0], buf); - + 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); } } diff --git a/src/tui/instance.rs b/src/tui/instance.rs index 8537ba8..ee5c575 100644 --- a/src/tui/instance.rs +++ b/src/tui/instance.rs @@ -1,29 +1,91 @@ use ratatui::{ + buffer::Buffer, + layout::Rect, style::Style, - widgets::{Block, Borders, Paragraph, Widget}, + widgets::{Block, Borders, Paragraph, StatefulWidget, Widget}, }; -pub struct InstanceSelect { +use crate::types::RootConfig; + +#[derive(Debug)] +pub struct InstanceSelectState { selected: Option, } -impl InstanceSelect { - pub fn new(selected: Option) -> Self { - Self { selected } +impl InstanceSelectState { + pub fn new() -> Self { + Self { selected: None } + } + + pub fn instance(&self) -> Option<&str> { + self.selected.as_deref() + } + + pub fn next_instance(&mut self, root_config: &RootConfig) { + let mut instances = root_config.instances(); + instances.sort(); + + if instances.is_empty() { + self.selected = None; + return; + } + + let next = match &self.selected { + None => instances.first().cloned(), + Some(curr) => { + let idx = instances.iter().position(|x| x == curr); + match idx { + Some(i) => { + let next_index = (i + 1) % instances.len(); + instances.get(next_index).cloned() + } + None => instances.first().cloned(), + } + } + }; + + self.selected = next; + } + + pub fn prev_instance(&mut self, root_config: &RootConfig) { + let mut instances = root_config.instances(); + instances.sort(); + + if instances.is_empty() { + self.selected = None; + return; + } + + let prev = match &self.selected { + None => instances.last().cloned(), + Some(curr) => { + let idx = instances.iter().position(|x| x == curr); + match idx { + Some(i) => { + let prev_index = if i == 0 { instances.len() - 1 } else { i - 1 }; + instances.get(prev_index).cloned() + } + None => Some(instances[instances.len() - 1].clone()), + } + } + }; + + self.selected = prev; } } -impl Widget for InstanceSelect { - fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) - where - Self: Sized, - { +pub struct InstanceSelect {} + +impl StatefulWidget for InstanceSelect { + type State = InstanceSelectState; + + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { let list_block = Block::default() .title("Instance") .borders(Borders::ALL) .style(Style::default()); - Paragraph::new(self.selected.unwrap_or("None".to_owned())) + Paragraph::new(state.selected.clone().unwrap_or("None".to_owned())) .block(list_block) .render(area, buf); }