Implement table row selection
This commit is contained in:
parent
44edfbd0c9
commit
bbfa0f1194
@ -30,4 +30,6 @@ wayfarer --path <path-to-savefile>
|
||||
| q | Normal | Quits the application |
|
||||
| o | Normal | Open a new file |
|
||||
| w | Normal | Toggle file watcher mode (requires "watch" feature) |
|
||||
| e | Normal | Enter edit mode |
|
||||
| H,J,K,L | Edit | Move between sections |
|
||||
| h,j,k,l | Edit | Move inside the current section |
|
||||
|
@ -19,7 +19,7 @@ use crossterm::terminal::{
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
use self::state::{Mode, Section, State};
|
||||
use self::state::{Mode, State};
|
||||
|
||||
|
||||
type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>;
|
||||
@ -33,6 +33,15 @@ pub struct Args {
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum Message {
|
||||
@ -47,13 +56,9 @@ pub enum Message {
|
||||
|
||||
ReloadFile,
|
||||
|
||||
MoveSectionLeft,
|
||||
MoveSection(Direction),
|
||||
|
||||
MoveSectionDown,
|
||||
|
||||
MoveSectionUp,
|
||||
|
||||
MoveSectionRight,
|
||||
MoveCur(Direction),
|
||||
}
|
||||
|
||||
|
||||
@ -117,6 +122,10 @@ fn handle_message(
|
||||
message: Message,
|
||||
) -> Result<()> {
|
||||
match message {
|
||||
Message::SetMode(Mode::Edit) => {
|
||||
state.edit_current_file();
|
||||
}
|
||||
|
||||
Message::SetMode(mode) => {
|
||||
debug!("Setting mode to {:?}", mode);
|
||||
|
||||
@ -159,36 +168,12 @@ fn handle_message(
|
||||
state.reload_active_savefile()?;
|
||||
}
|
||||
|
||||
Message::MoveSectionLeft => {
|
||||
state.active_section = match state.active_section {
|
||||
Section::Companions => Section::General,
|
||||
_ => Section::Companions,
|
||||
};
|
||||
Message::MoveSection(direction) => {
|
||||
state.move_section(direction);
|
||||
}
|
||||
|
||||
Message::MoveSectionRight => {
|
||||
state.active_section = match state.active_section {
|
||||
Section::Companions => Section::General,
|
||||
_ => Section::Companions,
|
||||
}
|
||||
}
|
||||
|
||||
Message::MoveSectionDown => {
|
||||
state.active_section = match state.active_section {
|
||||
Section::General => Section::Glyphs,
|
||||
Section::Glyphs => Section::Murals,
|
||||
Section::Murals => Section::General,
|
||||
section => section,
|
||||
};
|
||||
}
|
||||
|
||||
Message::MoveSectionUp => {
|
||||
state.active_section = match state.active_section {
|
||||
Section::General => Section::Murals,
|
||||
Section::Glyphs => Section::General,
|
||||
Section::Murals => Section::Glyphs,
|
||||
section => section,
|
||||
}
|
||||
Message::MoveCur(direction) => {
|
||||
state.move_in_current_section(direction);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
|
@ -7,7 +7,7 @@ use tracing::debug;
|
||||
use tui_input::backend::crossterm::EventHandler;
|
||||
use tui_input::Input;
|
||||
|
||||
use super::{Message, Mode, State};
|
||||
use super::{Direction, Message, Mode, State};
|
||||
|
||||
|
||||
pub fn handle(event_queue: &mut mpsc::Sender<Message>, state: &mut State) -> Result<()> {
|
||||
@ -78,19 +78,35 @@ fn handle_keyboard_input(
|
||||
}
|
||||
|
||||
(Mode::Edit, KeyCode::Char('H')) => {
|
||||
msg_tx.send(Message::MoveSectionLeft)?;
|
||||
msg_tx.send(Message::MoveSection(Direction::Left))?;
|
||||
}
|
||||
|
||||
(Mode::Edit, KeyCode::Char('J')) => {
|
||||
msg_tx.send(Message::MoveSectionDown)?;
|
||||
msg_tx.send(Message::MoveSection(Direction::Down))?;
|
||||
}
|
||||
|
||||
(Mode::Edit, KeyCode::Char('K')) => {
|
||||
msg_tx.send(Message::MoveSectionUp)?;
|
||||
msg_tx.send(Message::MoveSection(Direction::Up))?;
|
||||
}
|
||||
|
||||
(Mode::Edit, KeyCode::Char('L')) => {
|
||||
msg_tx.send(Message::MoveSectionRight)?;
|
||||
msg_tx.send(Message::MoveSection(Direction::Right))?;
|
||||
}
|
||||
|
||||
(Mode::Edit, KeyCode::Char('h')) => {
|
||||
msg_tx.send(Message::MoveCur(Direction::Left))?;
|
||||
}
|
||||
|
||||
(Mode::Edit, KeyCode::Char('j')) => {
|
||||
msg_tx.send(Message::MoveCur(Direction::Down))?;
|
||||
}
|
||||
|
||||
(Mode::Edit, KeyCode::Char('k')) => {
|
||||
msg_tx.send(Message::MoveCur(Direction::Up))?;
|
||||
}
|
||||
|
||||
(Mode::Edit, KeyCode::Char('l')) => {
|
||||
msg_tx.send(Message::MoveCur(Direction::Right))?;
|
||||
}
|
||||
|
||||
(Mode::Normal, KeyCode::Char('e')) => {
|
||||
|
@ -5,9 +5,12 @@ use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use jrny_save::Savefile;
|
||||
use ratatui::widgets::TableState;
|
||||
use tracing::debug;
|
||||
use tui_input::Input;
|
||||
|
||||
use super::view::info::{GLYPHS_TABLE_RANGE, MURALS_TABLE_RANGE, STATS_TABLE_RANGE};
|
||||
use super::Direction;
|
||||
#[cfg(feature = "watch")]
|
||||
use crate::watcher::FileWatcher;
|
||||
use crate::DIRS;
|
||||
@ -39,7 +42,11 @@ pub enum Section {
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
savefile: Option<Savefile>,
|
||||
original_file: Option<Savefile>,
|
||||
pub active_section: Section,
|
||||
pub stats_table: TableState,
|
||||
pub glyphs_table: TableState,
|
||||
pub murals_table: TableState,
|
||||
pub mode: Mode,
|
||||
pub file_select: Input,
|
||||
#[cfg(feature = "watch")]
|
||||
@ -95,6 +102,16 @@ impl State {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn edit_current_file(&mut self) {
|
||||
self.original_file = self.savefile.clone();
|
||||
self.select_section(self.active_section);
|
||||
self.mode = Mode::Edit;
|
||||
}
|
||||
|
||||
pub fn is_savefile_loaded(&self) -> bool {
|
||||
self.savefile().is_some()
|
||||
}
|
||||
|
||||
#[cfg(feature = "watch")]
|
||||
pub fn is_watching_file(&self) -> bool {
|
||||
self.file_watcher.is_some()
|
||||
@ -125,6 +142,69 @@ impl State {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn move_section(&mut self, direction: Direction) {
|
||||
let next_section = match (direction, self.active_section) {
|
||||
(Direction::Left, Section::Companions) => Section::General,
|
||||
(Direction::Left, _) => Section::Companions,
|
||||
(Direction::Right, Section::Companions) => Section::General,
|
||||
(Direction::Right, _) => Section::Companions,
|
||||
(Direction::Down, Section::General) => Section::Glyphs,
|
||||
(Direction::Down, Section::Glyphs) => Section::Murals,
|
||||
(Direction::Down, Section::Murals) => Section::General,
|
||||
(Direction::Down, section) => section,
|
||||
(Direction::Up, Section::General) => Section::Murals,
|
||||
(Direction::Up, Section::Murals) => Section::Glyphs,
|
||||
(Direction::Up, Section::Glyphs) => Section::General,
|
||||
(Direction::Up, section) => section,
|
||||
};
|
||||
|
||||
self.select_section(next_section);
|
||||
self.active_section = next_section;
|
||||
}
|
||||
|
||||
fn select_section(&mut self, section: Section) {
|
||||
let table = match section {
|
||||
Section::General => &mut self.stats_table,
|
||||
Section::Glyphs => &mut self.glyphs_table,
|
||||
Section::Murals => &mut self.murals_table,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if table.selected().is_none() {
|
||||
table.select(Some(0));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_in_current_section(&mut self, direction: Direction) {
|
||||
match self.active_section {
|
||||
Section::General => {
|
||||
select_row_in_range(&mut self.stats_table, direction, STATS_TABLE_RANGE)
|
||||
}
|
||||
Section::Glyphs => {
|
||||
select_row_in_range(&mut self.glyphs_table, direction, GLYPHS_TABLE_RANGE)
|
||||
}
|
||||
Section::Murals => {
|
||||
select_row_in_range(&mut self.murals_table, direction, MURALS_TABLE_RANGE)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn select_row_in_range(table: &mut TableState, direction: Direction, (min, max): (usize, usize)) {
|
||||
match (direction, table.selected()) {
|
||||
(Direction::Up, Some(i)) if i <= min => (),
|
||||
(Direction::Up, Some(i)) => {
|
||||
table.select(Some(i - 1));
|
||||
}
|
||||
(Direction::Down, Some(i)) if i >= max => (),
|
||||
(Direction::Down, Some(i)) => {
|
||||
table.select(Some(i + 1));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
mod info;
|
||||
mod status_bar;
|
||||
pub mod info;
|
||||
pub mod status_bar;
|
||||
|
||||
|
||||
use std::io::Stdout;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use jrny_save::{Savefile, LEVEL_NAMES};
|
||||
use jrny_save::LEVEL_NAMES;
|
||||
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
|
||||
use ratatui::style::Style;
|
||||
use ratatui::style::{Color, Style};
|
||||
use ratatui::widgets::{Block, Borders, Cell, Padding, Paragraph, Row, Table};
|
||||
|
||||
use crate::tui::state::{Mode, Section};
|
||||
@ -8,10 +8,16 @@ use crate::tui::view::Frame;
|
||||
use crate::tui::State;
|
||||
|
||||
|
||||
pub const STATS_TABLE_RANGE: (usize, usize) = (0, 9);
|
||||
pub const GLYPHS_TABLE_RANGE: (usize, usize) = (0, 5);
|
||||
pub const MURALS_TABLE_RANGE: (usize, usize) = (0, 6);
|
||||
|
||||
|
||||
pub fn render(state: &mut State, frame: &mut Frame, area: Rect) {
|
||||
match state.savefile() {
|
||||
Some(savefile) => render_info(savefile, state, frame, area),
|
||||
None => render_no_active_file(frame, area),
|
||||
if state.is_savefile_loaded() {
|
||||
render_info(state, frame, area);
|
||||
} else {
|
||||
render_no_active_file(frame, area);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,7 +34,7 @@ fn render_no_active_file(frame: &mut Frame, area: Rect) {
|
||||
}
|
||||
|
||||
|
||||
fn render_info(savefile: &Savefile, state: &State, mut frame: &mut Frame, area: Rect) {
|
||||
fn render_info(state: &mut State, mut frame: &mut Frame, area: Rect) {
|
||||
let columns = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
@ -48,16 +54,22 @@ fn render_info(savefile: &Savefile, state: &State, mut frame: &mut Frame, area:
|
||||
.constraints([Constraint::Ratio(10, 10)])
|
||||
.split(columns[1]);
|
||||
|
||||
render_stats(&savefile, state, &mut frame, left_column[0]);
|
||||
render_glyphs(&savefile, state, &mut frame, left_column[1]);
|
||||
render_murals(&savefile, state, &mut frame, left_column[2]);
|
||||
render_companions(&savefile, state, &mut frame, right_column[0]);
|
||||
render_stats(state, &mut frame, left_column[0]);
|
||||
render_glyphs(state, &mut frame, left_column[1]);
|
||||
render_murals(state, &mut frame, left_column[2]);
|
||||
render_companions(state, &mut frame, right_column[0]);
|
||||
}
|
||||
|
||||
|
||||
fn render_stats<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area: Rect) {
|
||||
let border_style = if state.active_section == Section::General && state.mode == Mode::Edit {
|
||||
Style::default().fg(ratatui::style::Color::Blue)
|
||||
fn render_stats<'a>(state: &mut State, frame: &mut Frame, area: Rect) {
|
||||
let Some(savefile) = state.savefile() else {
|
||||
return
|
||||
};
|
||||
|
||||
let is_selected = state.active_section == Section::General && state.mode == Mode::Edit;
|
||||
|
||||
let border_style = if is_selected {
|
||||
Style::default().fg(Color::Blue)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
@ -74,6 +86,12 @@ fn render_stats<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area:
|
||||
|
||||
let stats_block = Block::default().title("Stats");
|
||||
|
||||
let table_highlight = if is_selected {
|
||||
Style::default().fg(Color::Blue)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
|
||||
let table = Table::new([
|
||||
Row::new([
|
||||
"Journeys Completed".to_string(),
|
||||
@ -101,6 +119,7 @@ fn render_stats<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area:
|
||||
Row::new(["Robe Tier".to_string(), savefile.robe_tier().to_string()]),
|
||||
Row::new(["Last Played".to_string(), savefile.last_played.to_string()]),
|
||||
])
|
||||
.highlight_style(table_highlight)
|
||||
.widths(&[Constraint::Ratio(1, 3), Constraint::Ratio(2, 3)])
|
||||
.block(stats_block);
|
||||
|
||||
@ -110,12 +129,18 @@ fn render_stats<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area:
|
||||
|
||||
frame.render_widget(stats_section_block, area);
|
||||
frame.render_widget(cur_symbol, layout[0]);
|
||||
frame.render_widget(table, layout[1]);
|
||||
frame.render_stateful_widget(table, layout[1], &mut state.stats_table);
|
||||
}
|
||||
|
||||
|
||||
fn render_companions<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area: Rect) {
|
||||
let border_style = if state.active_section == Section::Companions && state.mode == Mode::Edit {
|
||||
fn render_companions<'a>(state: &State, frame: &mut Frame, area: Rect) {
|
||||
let Some(savefile) = state.savefile() else {
|
||||
return
|
||||
};
|
||||
|
||||
let is_selected = state.active_section == Section::Companions && state.mode == Mode::Edit;
|
||||
|
||||
let border_style = if is_selected {
|
||||
Style::default().fg(ratatui::style::Color::Blue)
|
||||
} else {
|
||||
Style::default()
|
||||
@ -164,12 +189,19 @@ fn render_companions<'a>(savefile: &Savefile, state: &State, frame: &mut Frame,
|
||||
}
|
||||
|
||||
|
||||
fn render_glyphs<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area: Rect) {
|
||||
fn render_glyphs<'a>(state: &mut State, frame: &mut Frame, area: Rect) {
|
||||
const FOUND_SIGN: &str = "◆";
|
||||
const NOT_FOUND_SIGN: &str = "◇";
|
||||
|
||||
let border_style = if state.active_section == Section::Glyphs && state.mode == Mode::Edit {
|
||||
Style::default().fg(ratatui::style::Color::Blue)
|
||||
let Some(savefile) = state.savefile() else {
|
||||
return
|
||||
};
|
||||
|
||||
let is_selected = state.active_section == Section::Glyphs && state.mode == Mode::Edit;
|
||||
|
||||
|
||||
let border_style = if is_selected {
|
||||
Style::default().fg(Color::Blue)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
@ -180,6 +212,12 @@ fn render_glyphs<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area
|
||||
.borders(Borders::ALL)
|
||||
.padding(Padding::new(2, 2, 1, 1));
|
||||
|
||||
let table_highlight = if is_selected {
|
||||
Style::default().fg(Color::Blue)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
|
||||
let table = Table::new(savefile.glyphs.all().map(|(level_number, status)| {
|
||||
let status = status
|
||||
.iter()
|
||||
@ -198,18 +236,25 @@ fn render_glyphs<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.column_spacing(1)
|
||||
.highlight_style(table_highlight)
|
||||
.block(block);
|
||||
|
||||
frame.render_widget(table, area);
|
||||
frame.render_stateful_widget(table, area, &mut state.glyphs_table);
|
||||
}
|
||||
|
||||
|
||||
fn render_murals<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area: Rect) {
|
||||
fn render_murals<'a>(state: &mut State, frame: &mut Frame, area: Rect) {
|
||||
const FOUND_SIGN: &str = "▾";
|
||||
const NOT_FOUND_SIGN: &str = "▿";
|
||||
|
||||
let border_style = if state.active_section == Section::Murals && state.mode == Mode::Edit {
|
||||
Style::default().fg(ratatui::style::Color::Blue)
|
||||
let Some(savefile) = state.savefile() else {
|
||||
return
|
||||
};
|
||||
|
||||
let is_selected = state.active_section == Section::Murals && state.mode == Mode::Edit;
|
||||
|
||||
let border_style = if is_selected {
|
||||
Style::default().fg(Color::Blue)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
@ -220,6 +265,12 @@ fn render_murals<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area
|
||||
.borders(Borders::ALL)
|
||||
.padding(Padding::new(2, 2, 1, 1));
|
||||
|
||||
let table_highlight = if is_selected {
|
||||
Style::default().fg(Color::Blue)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
|
||||
let table = Table::new(savefile.murals.all().map(|(level_number, status)| {
|
||||
let status = status
|
||||
.iter()
|
||||
@ -238,7 +289,8 @@ fn render_murals<'a>(savefile: &Savefile, state: &State, frame: &mut Frame, area
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.column_spacing(1)
|
||||
.highlight_style(table_highlight)
|
||||
.block(block);
|
||||
|
||||
frame.render_widget(table, area);
|
||||
frame.render_stateful_widget(table, area, &mut state.murals_table);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user