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 crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use log::error;
use ratatui::{
DefaultTerminal, Frame,
layout::{Constraint, Direction, Layout, Rect},
style::Style,
widgets::{Block, Borders, StatefulWidget, TableState, Widget},
widgets::{StatefulWidget, TableState, Widget},
};
use crate::{
tui::{instance::InstanceSelect, mod_list::ModList, status::StatusBar},
types::RootConfig,
tui::{
instance::{InstanceSelect, InstanceSelectState},
mod_list::ModList,
status::StatusBar,
},
types::{ModdedInstance, RootConfig},
};
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> {
root_config: &'a mut RootConfig,
loaded_instance: Option<ModdedInstance>,
exit: bool,
active_modal: bool,
mod_list_state: TableState,
selected_instance: Option<String>,
selected_instance_state: InstanceSelectState,
}
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,
selected_instance: None,
loaded_instance: None,
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();
}
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') => {
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;
}
fn next_instance(&mut self) {
let mut instances = self.root_config.instances();
instances.sort();
if instances.is_empty() {
self.selected_instance = None;
fn load_instance(&mut self) {
let Some(selected) = self.selected_instance_state.instance() else {
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.exit();
}
}
};
self.selected_instance = prev;
}
}
@@ -152,14 +122,12 @@ impl<'a> Widget for &mut App<'a> {
.constraints([
Constraint::Length(3),
Constraint::Min(1),
Constraint::Length(1), // single line for keybindings
Constraint::Length(1),
])
.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);
StatusBar.render(chunks[2], buf);
}
}

View File

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