added basic tui

This commit is contained in:
2026-03-23 14:43:47 +01:00
parent 3f91386763
commit ac7b07ee3d
9 changed files with 1164 additions and 29 deletions

95
src/tui/app.rs Normal file
View File

@@ -0,0 +1,95 @@
use std::io;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use ratatui::{
DefaultTerminal, Frame,
layout::Rect,
style::Style,
widgets::{Block, Borders, StatefulWidget, TableState, Widget},
};
use crate::{tui::mod_list::ModList, types::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,
exit: bool,
mod_list_state: TableState,
}
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,
exit: false,
}
}
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();
}
_ => {}
}
}
fn exit(&mut self) {
self.exit = true;
}
}
impl<'a> Widget for &mut App<'a> {
fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer)
where
Self: Sized,
{
let list_block = Block::default()
.title("Mod list")
.borders(Borders::ALL)
.style(Style::default());
ModList::new(self.root_config).block(list_block).render(
area,
buf,
&mut self.mod_list_state,
);
}
}

74
src/tui/mod_list.rs Normal file
View File

@@ -0,0 +1,74 @@
use ratatui::{
buffer::Buffer,
layout::{Constraint, Rect},
style::{Color, Modifier, Style},
widgets::{Block, Cell, Row, StatefulWidget, Table, TableState},
};
use crate::types::{ModConfig, RootConfig};
#[derive(Debug)]
pub struct ListItem<'a> {
mod_config: &'a ModConfig,
id: &'a str,
}
#[derive(Debug)]
pub struct ModList<'a> {
items: Vec<ListItem<'a>>,
block: Option<Block<'a>>,
}
impl<'a> ModList<'a> {
pub fn new(root_config: &'a RootConfig) -> Self {
let mut items: Vec<_> = root_config
.mods()
.iter()
.map(|(id, config)| ListItem {
id,
mod_config: config,
})
.collect();
items.sort_by_key(|item| item.id);
Self { items, block: None }
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = Some(block);
self
}
}
impl<'a> StatefulWidget for ModList<'a> {
type State = TableState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let rows: Vec<Row> = self
.items
.iter()
.map(|item| {
Row::new(vec![
Cell::from(item.mod_config.name().unwrap_or(item.id)),
Cell::from(item.id),
])
})
.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),
)
.highlight_symbol(">> ");
let table = match self.block {
Some(b) => table.block(b),
None => table,
};
StatefulWidget::render(table, area, buf, state);
}
}