diff --git a/src/tui/app.rs b/src/tui/app.rs index eda6689..b4e439d 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -5,13 +5,13 @@ use log::error; use ratatui::{ DefaultTerminal, Frame, layout::{Constraint, Direction, Layout, Rect}, - widgets::{StatefulWidget, TableState, Widget}, + widgets::{StatefulWidget, Widget}, }; use crate::{ tui::{ instance::{InstanceSelect, InstanceSelectState}, - mod_list::ModList, + mod_list::{ModList, ModListState}, status::StatusBar, }, types::{ModdedInstance, RootConfig}, @@ -30,19 +30,21 @@ struct App<'a> { exit: bool, - mod_list_state: TableState, + mod_list_state: ModListState, selected_instance_state: InstanceSelectState, } impl<'a> App<'a> { fn new(root_config: &'a mut RootConfig) -> Self { + let mut mod_list_state = ModListState::new(); + mod_list_state.update_list(root_config, None); Self { root_config, loaded_instance: None, exit: false, - mod_list_state: TableState::default(), + mod_list_state, selected_instance_state: InstanceSelectState::new(), } } @@ -74,7 +76,7 @@ impl<'a> App<'a> { match key_event.code { KeyCode::Esc | KeyCode::Char('q') => self.exit(), KeyCode::Up | KeyCode::Char('k') => { - self.mod_list_state.select_previous(); + self.mod_list_state.select_prev(); } KeyCode::Down | KeyCode::Char('j') => { self.mod_list_state.select_next(); @@ -103,6 +105,8 @@ impl<'a> App<'a> { match self.root_config.load_instance_by_id(selected) { Ok(instance) => { self.loaded_instance = Some(instance); + self.mod_list_state + .update_list(self.root_config, self.loaded_instance.as_ref()); } Err(err) => { error!("Failed to load instance: {err}"); @@ -126,8 +130,8 @@ impl<'a> Widget for &mut App<'a> { ]) .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); + InstanceSelect.render(chunks[0], buf, &mut self.selected_instance_state); + ModList.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 ee5c575..bd7345d 100644 --- a/src/tui/instance.rs +++ b/src/tui/instance.rs @@ -74,7 +74,7 @@ impl InstanceSelectState { } } -pub struct InstanceSelect {} +pub struct InstanceSelect; impl StatefulWidget for InstanceSelect { type State = InstanceSelectState; diff --git a/src/tui/mod_list.rs b/src/tui/mod_list.rs index ac720c7..d335683 100644 --- a/src/tui/mod_list.rs +++ b/src/tui/mod_list.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use ratatui::{ buffer::Buffer, layout::{Constraint, Rect}, @@ -5,37 +7,73 @@ use ratatui::{ widgets::{Block, Borders, Cell, Row, StatefulWidget, Table, TableState}, }; -use crate::types::{ModConfig, RootConfig}; +use crate::types::{ModConfig, ModdedInstance, RootConfig}; #[derive(Debug)] -pub struct ListItem<'a> { - mod_config: &'a ModConfig, - id: &'a str, +pub struct ModListState { + table_state: TableState, + items: Vec, } -#[derive(Debug)] -pub struct ModList<'a> { - items: Vec>, -} +impl ModListState { + pub fn new() -> Self { + Self { + table_state: TableState::new(), + items: Vec::new(), + } + } + + pub fn select_next(&mut self) { + self.table_state.select_next(); + } + + pub fn select_prev(&mut self) { + self.table_state.select_previous(); + } + + pub fn update_list( + &mut self, + root_config: &RootConfig, + loaded_instance: Option<&ModdedInstance>, + ) { + let instance_game_type = loaded_instance + .and_then(|e| root_config.game_by_id(e.game_id())) + .map(|e| e.game_type()); + + let included_ids: Option> = + loaded_instance.map(|instance| instance.mods().iter().map(|m| m.mod_id()).collect()); -impl<'a> ModList<'a> { - pub fn new(root_config: &'a RootConfig) -> Self { let mut items: Vec<_> = root_config .mods() .iter() + .filter(|e| instance_game_type.clone().is_none_or(|gt| e.1.game() == gt)) .map(|(id, config)| ListItem { - id, - mod_config: config, + id: id.to_owned(), + mod_config: config.clone(), + included: included_ids + .as_ref() + .is_some_and(|set| set.contains(id.as_str())), }) .collect(); - items.sort_by_key(|item| item.id); - Self { items } + items.sort_by_key(|item| item.id.clone()); + + self.items = items; } } -impl<'a> StatefulWidget for ModList<'a> { - type State = TableState; +#[derive(Debug)] +struct ListItem { + mod_config: ModConfig, + id: String, + included: bool, +} + +#[derive(Debug)] +pub struct ModList; + +impl StatefulWidget for ModList { + type State = ModListState; fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { let block = Block::default() @@ -43,27 +81,34 @@ impl<'a> StatefulWidget for ModList<'a> { .borders(Borders::ALL) .style(Style::default()); - let rows: Vec = self + let rows: Vec = state .items .iter() .map(|item| { Row::new(vec![ - Cell::from(item.mod_config.name().unwrap_or(item.id)), - Cell::from(item.id), + Cell::from(item.mod_config.name().unwrap_or(&item.id)), + Cell::from(item.id.as_str()), + Cell::from(if item.included { "" } else { "" }), ]) }) .collect(); - let table = Table::new(rows, [Constraint::Fill(1), Constraint::Fill(1)]) - .row_highlight_style( - Style::default() - .fg(Color::Yellow) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ) - .block(block) - .highlight_symbol(">> "); + let table = Table::new( + rows, + [ + Constraint::Fill(1), + Constraint::Fill(1), + Constraint::Fill(1), + ], + ) + .row_highlight_style( + Style::default() + .fg(Color::Yellow) + .bg(Color::DarkGray) + .add_modifier(Modifier::BOLD), + ) + .block(block); - StatefulWidget::render(table, area, buf, state); + StatefulWidget::render(table, area, buf, &mut state.table_state); } }