added basic tui
This commit is contained in:
95
src/tui/app.rs
Normal file
95
src/tui/app.rs
Normal 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
74
src/tui/mod_list.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user