improved mod list

This commit is contained in:
2026-03-29 15:36:53 +02:00
parent 49f38cb21a
commit ea50f4d59b
3 changed files with 86 additions and 37 deletions

View File

@@ -5,13 +5,13 @@ use log::error;
use ratatui::{ use ratatui::{
DefaultTerminal, Frame, DefaultTerminal, Frame,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
widgets::{StatefulWidget, TableState, Widget}, widgets::{StatefulWidget, Widget},
}; };
use crate::{ use crate::{
tui::{ tui::{
instance::{InstanceSelect, InstanceSelectState}, instance::{InstanceSelect, InstanceSelectState},
mod_list::ModList, mod_list::{ModList, ModListState},
status::StatusBar, status::StatusBar,
}, },
types::{ModdedInstance, RootConfig}, types::{ModdedInstance, RootConfig},
@@ -30,19 +30,21 @@ struct App<'a> {
exit: bool, exit: bool,
mod_list_state: TableState, mod_list_state: ModListState,
selected_instance_state: InstanceSelectState, selected_instance_state: InstanceSelectState,
} }
impl<'a> App<'a> { impl<'a> App<'a> {
fn new(root_config: &'a mut RootConfig) -> Self { fn new(root_config: &'a mut RootConfig) -> Self {
let mut mod_list_state = ModListState::new();
mod_list_state.update_list(root_config, None);
Self { Self {
root_config, root_config,
loaded_instance: None, loaded_instance: None,
exit: false, exit: false,
mod_list_state: TableState::default(), mod_list_state,
selected_instance_state: InstanceSelectState::new(), selected_instance_state: InstanceSelectState::new(),
} }
} }
@@ -74,7 +76,7 @@ impl<'a> App<'a> {
match key_event.code { match key_event.code {
KeyCode::Esc | KeyCode::Char('q') => self.exit(), KeyCode::Esc | KeyCode::Char('q') => self.exit(),
KeyCode::Up | KeyCode::Char('k') => { KeyCode::Up | KeyCode::Char('k') => {
self.mod_list_state.select_previous(); self.mod_list_state.select_prev();
} }
KeyCode::Down | KeyCode::Char('j') => { KeyCode::Down | KeyCode::Char('j') => {
self.mod_list_state.select_next(); self.mod_list_state.select_next();
@@ -103,6 +105,8 @@ impl<'a> App<'a> {
match self.root_config.load_instance_by_id(selected) { match self.root_config.load_instance_by_id(selected) {
Ok(instance) => { Ok(instance) => {
self.loaded_instance = Some(instance); self.loaded_instance = Some(instance);
self.mod_list_state
.update_list(self.root_config, self.loaded_instance.as_ref());
} }
Err(err) => { Err(err) => {
error!("Failed to load instance: {err}"); error!("Failed to load instance: {err}");
@@ -126,8 +130,8 @@ impl<'a> Widget for &mut App<'a> {
]) ])
.split(area); .split(area);
InstanceSelect {}.render(chunks[0], buf, &mut self.selected_instance_state); InstanceSelect.render(chunks[0], buf, &mut self.selected_instance_state);
ModList::new(self.root_config).render(chunks[1], buf, &mut self.mod_list_state); ModList.render(chunks[1], buf, &mut self.mod_list_state);
StatusBar.render(chunks[2], buf); StatusBar.render(chunks[2], buf);
} }
} }

View File

@@ -74,7 +74,7 @@ impl InstanceSelectState {
} }
} }
pub struct InstanceSelect {} pub struct InstanceSelect;
impl StatefulWidget for InstanceSelect { impl StatefulWidget for InstanceSelect {
type State = InstanceSelectState; type State = InstanceSelectState;

View File

@@ -1,3 +1,5 @@
use std::collections::HashSet;
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Constraint, Rect}, layout::{Constraint, Rect},
@@ -5,37 +7,73 @@ use ratatui::{
widgets::{Block, Borders, Cell, Row, StatefulWidget, Table, TableState}, widgets::{Block, Borders, Cell, Row, StatefulWidget, Table, TableState},
}; };
use crate::types::{ModConfig, RootConfig}; use crate::types::{ModConfig, ModdedInstance, RootConfig};
#[derive(Debug)] #[derive(Debug)]
pub struct ListItem<'a> { pub struct ModListState {
mod_config: &'a ModConfig, table_state: TableState,
id: &'a str, items: Vec<ListItem>,
} }
#[derive(Debug)] impl ModListState {
pub struct ModList<'a> { pub fn new() -> Self {
items: Vec<ListItem<'a>>, 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<HashSet<_>> =
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 let mut items: Vec<_> = root_config
.mods() .mods()
.iter() .iter()
.filter(|e| instance_game_type.clone().is_none_or(|gt| e.1.game() == gt))
.map(|(id, config)| ListItem { .map(|(id, config)| ListItem {
id, id: id.to_owned(),
mod_config: config, mod_config: config.clone(),
included: included_ids
.as_ref()
.is_some_and(|set| set.contains(id.as_str())),
}) })
.collect(); .collect();
items.sort_by_key(|item| item.id); items.sort_by_key(|item| item.id.clone());
Self { items }
self.items = items;
} }
} }
impl<'a> StatefulWidget for ModList<'a> { #[derive(Debug)]
type State = TableState; 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) { fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let block = Block::default() let block = Block::default()
@@ -43,27 +81,34 @@ impl<'a> StatefulWidget for ModList<'a> {
.borders(Borders::ALL) .borders(Borders::ALL)
.style(Style::default()); .style(Style::default());
let rows: Vec<Row> = self let rows: Vec<Row> = state
.items .items
.iter() .iter()
.map(|item| { .map(|item| {
Row::new(vec![ Row::new(vec![
Cell::from(item.mod_config.name().unwrap_or(item.id)), Cell::from(item.mod_config.name().unwrap_or(&item.id)),
Cell::from(item.id), Cell::from(item.id.as_str()),
Cell::from(if item.included { "" } else { "" }),
]) ])
}) })
.collect(); .collect();
let table = Table::new(rows, [Constraint::Fill(1), Constraint::Fill(1)]) let table = Table::new(
.row_highlight_style( rows,
Style::default() [
.fg(Color::Yellow) Constraint::Fill(1),
.bg(Color::DarkGray) Constraint::Fill(1),
.add_modifier(Modifier::BOLD), Constraint::Fill(1),
) ],
.block(block) )
.highlight_symbol(">> "); .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);
} }
} }