handle instance select in own state

This commit is contained in:
2026-03-28 21:23:13 +01:00
parent 93676901c0
commit e70c6e6901
2 changed files with 105 additions and 75 deletions

View File

@@ -1,16 +1,20 @@
use std::io; use std::io;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use log::error;
use ratatui::{ use ratatui::{
DefaultTerminal, Frame, DefaultTerminal, Frame,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
style::Style, widgets::{StatefulWidget, TableState, Widget},
widgets::{Block, Borders, StatefulWidget, TableState, Widget},
}; };
use crate::{ use crate::{
tui::{instance::InstanceSelect, mod_list::ModList, status::StatusBar}, tui::{
types::RootConfig, instance::{InstanceSelect, InstanceSelectState},
mod_list::ModList,
status::StatusBar,
},
types::{ModdedInstance, RootConfig},
}; };
pub fn run(root_config: &mut RootConfig) -> anyhow::Result<()> { 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> { struct App<'a> {
root_config: &'a mut RootConfig, root_config: &'a mut RootConfig,
loaded_instance: Option<ModdedInstance>,
exit: bool, exit: bool,
active_modal: bool,
mod_list_state: TableState, mod_list_state: TableState,
selected_instance: Option<String>, 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 state = TableState::default();
state.select(Some(0)); // select first row by default
Self { Self {
root_config, root_config,
mod_list_state: state,
selected_instance: None, loaded_instance: None,
exit: false, 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(); self.mod_list_state.select_next();
} }
KeyCode::Right | KeyCode::Char('l') => { 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') => { 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; self.exit = true;
} }
fn next_instance(&mut self) { fn load_instance(&mut self) {
let mut instances = self.root_config.instances(); let Some(selected) = self.selected_instance_state.instance() else {
instances.sort();
if instances.is_empty() {
self.selected_instance = None;
return; 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; match self.root_config.load_instance_by_id(selected) {
} Ok(instance) => {
self.loaded_instance = Some(instance);
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()),
}
} }
}; Err(err) => {
error!("Failed to load instance: {err}");
self.selected_instance = prev; self.exit();
}
}
} }
} }
@@ -152,14 +122,12 @@ impl<'a> Widget for &mut App<'a> {
.constraints([ .constraints([
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(1), Constraint::Min(1),
Constraint::Length(1), // single line for keybindings Constraint::Length(1),
]) ])
.split(area); .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); ModList::new(self.root_config).render(chunks[1], buf, &mut self.mod_list_state);
StatusBar.render(chunks[2], buf); StatusBar.render(chunks[2], buf);
} }
} }

View File

@@ -1,29 +1,91 @@
use ratatui::{ use ratatui::{
buffer::Buffer,
layout::Rect,
style::Style, 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<String>, selected: Option<String>,
} }
impl InstanceSelect { impl InstanceSelectState {
pub fn new(selected: Option<String>) -> Self { pub fn new() -> Self {
Self { selected } 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 { pub struct InstanceSelect {}
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
where impl StatefulWidget for InstanceSelect {
Self: Sized, type State = InstanceSelectState;
{
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let list_block = Block::default() let list_block = Block::default()
.title("Instance") .title("Instance")
.borders(Borders::ALL) .borders(Borders::ALL)
.style(Style::default()); .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) .block(list_block)
.render(area, buf); .render(area, buf);
} }