Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
francisdb committed Oct 18, 2024
1 parent 67ddc32 commit 6fadbff
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 28 deletions.
107 changes: 104 additions & 3 deletions src/frontend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@ pub mod update;

use crate::config::ResolvedConfig;
use crate::indexer::IndexedTable;
use crate::simplefrontend::{launch, TableOption};
use crate::simplefrontend::{launch, prompt, TableOption};
use crate::{info_diff, info_edit, write_info_json};
use anyhow::Result;
use colored::Colorize;
use event::{Event, EventHandler};
use ratatui::backend::CrosstermBackend;
use state::State;
use std::collections::HashSet;
use std::io::stdin;
use std::io::{stdin, Cursor, Write};
use tui::Tui;
use update::update;
use vpin::vpx::{extractvbs, ExtractResult};

type Terminal = ratatui::Terminal<CrosstermBackend<std::io::Stderr>>;

Expand Down Expand Up @@ -92,6 +95,86 @@ fn run_action(state: &mut State, tui: &mut Tui, action: Action) -> Result<bool>
launch(selected_path, vpinball_executable, Some(false));
Ok(())
}),
TableOption::InfoShow => run_external(tui, || {
// echo pipe to less
let mut memory_file = Cursor::new(Vec::new());
write_info_json(selected_path, &mut memory_file)?;
let output = memory_file.into_inner();
// execute less with the data piped in
let mut less = std::process::Command::new("less")
.stdin(std::process::Stdio::piped())
.spawn()?;
let mut stdin = less.stdin.take().unwrap();
stdin.write_all(&output)?;
// wait for less to finish
less.wait()?;
Ok(())
}),
TableOption::InfoEdit => run_external(tui, || {
let config = Some(&state.config);
match info_edit(selected_path, config) {
Ok(path) => {
println!("Launched editor for {}", path.display());
}
Err(err) => {
let msg = format!("Unable to edit table info: {}", err);
prompt(msg.truecolor(255, 125, 0).to_string());
}
}
Ok(())
}),
TableOption::InfoDiff => run_external(tui, || {
match info_diff(selected_path) {
Ok(diff) => {
prompt(diff);
}
Err(err) => {
let msg = format!("Unable to diff info: {}", err);
prompt(msg.truecolor(255, 125, 0).to_string());
}
};
Ok(())
}),
TableOption::ExtractVBS => {
// TODO is this guard thing a good idea? I prefer the closure approach
// but we got some issues with the borrow checker
let _guard = TuiGuard::new(tui)?;
match extractvbs(selected_path, false, None) {
Ok(ExtractResult::Extracted(path)) => {
state.prompt_info(format!(
"VBS extracted to {}",
path.to_string_lossy()
));
}
Ok(ExtractResult::Existed(path)) => {
let msg =
format!("VBS already exists at {}", path.to_string_lossy());
state.prompt_warning(msg);
}
Err(err) => {
let msg = format!("Unable to extract VBS: {}", err);
state.prompt_error(msg);
}
}
Ok(())
}
TableOption::EditVBS => run_external(tui, || {
let config = Some(&state.config);
match info_edit(selected_path, config) {
Ok(path) => {
println!("Launched editor for {}", path.display());
}
Err(err) => {
let msg = format!("Unable to edit table info: {}", err);
prompt(msg.truecolor(255, 125, 0).to_string());
}
}
Ok(())
}),
TableOption::PatchVBS => run_external(tui, || Ok(())),
TableOption::UnifyLineEndings => run_external(tui, || Ok(())),
TableOption::ShowVBSDiff => run_external(tui, || Ok(())),
TableOption::CreateVBSPatch => run_external(tui, || Ok(())),
not_implemented => run_external(tui, || {
eprintln!(
"Action not implemented: {:?}. Press enter to continue.",
Expand All @@ -112,8 +195,26 @@ fn run_action(state: &mut State, tui: &mut Tui, action: Action) -> Result<bool>
}
}

struct TuiGuard<'a> {
tui: &'a mut Tui,
}

impl<'a> TuiGuard<'a> {
fn new(tui: &'a mut Tui) -> Result<Self> {
tui.disable()?;
Ok(Self { tui })
}
}

impl<'a> Drop for TuiGuard<'a> {
fn drop(&mut self) {
if let Err(err) = self.tui.enable() {
eprintln!("Failed to re-enable TUI: {}", err);
}
}
}

fn run_external<T>(tui: &mut Tui, run: impl Fn() -> Result<T>) -> Result<T> {
// TODO most of this stuff is duplicated in Tui
tui.disable()?;
let result = run();
tui.enable()?;
Expand Down
33 changes: 33 additions & 0 deletions src/frontend/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,20 @@ pub struct State {
pub roms: HashSet<String>,
pub tables: TableList,
pub table_dialog: Option<TableActionsDialog>,
/// Dialog to display messages to the user.
/// It can be a warning or an error.
/// It will be displayed until the user dismisses it.
/// It will be displayed on top of everything else.
pub message_dialog: Option<MessageDialog>,
}

#[derive(Debug)]
pub enum MessageDialog {
Info(String),
Warning(String),
Error(String),
}

#[derive(Debug, Default)]
pub enum TablesSort {
#[default]
Expand Down Expand Up @@ -209,6 +222,7 @@ impl State {
roms,
tables,
table_dialog: None,
message_dialog: None,
}
}

Expand All @@ -217,6 +231,9 @@ impl State {

/// Returns the key bindings.
pub fn get_key_bindings(&self) -> Vec<(&str, &str)> {
if let Some(_) = self.message_dialog {
return vec![("⏎", "Dismiss")];
}
match self.table_dialog {
Some(_) => vec![("⏎", "Select"), ("↑↓", "Navigate"), ("q/esc", "Back")],
None => vec![
Expand All @@ -237,6 +254,22 @@ impl State {
self.table_dialog = Some(dialog);
}
}

pub(crate) fn close_dialog(&mut self) {
self.table_dialog = None;
}

pub(crate) fn prompt_info(&mut self, message: String) {
self.message_dialog = Some(MessageDialog::Info(message));
}

pub(crate) fn prompt_warning(&mut self, message: String) {
self.message_dialog = Some(MessageDialog::Warning(message));
}

pub(crate) fn prompt_error(&mut self, message: String) {
self.message_dialog = Some(MessageDialog::Error(message));
}
}

#[cfg(test)]
Expand Down
45 changes: 43 additions & 2 deletions src/frontend/ui.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::frontend::state::{FilterState, State, TableActionsDialog, TablesSort};
use crate::frontend::state::{FilterState, MessageDialog, State, TableActionsDialog, TablesSort};
use crate::indexer::IndexedTable;
use crate::simplefrontend::{capitalize_first_letter, TableOption};
use ratatui::layout::{Constraint, Direction, Flex, Layout, Rect};
Expand Down Expand Up @@ -29,7 +29,7 @@ const KEY_BINDING_STYLE: Style = Style::new().fg(AMBER.c500);

pub fn render(state: &mut State, f: &mut Frame) {
let mut main_enabled = true;
if state.table_dialog.is_some() {
if state.table_dialog.is_some() || state.message_dialog.is_some() {
main_enabled = false;
}

Expand All @@ -48,9 +48,37 @@ pub fn render(state: &mut State, f: &mut Frame) {
render_action_dialog(table_dialog, f, table);
}

// render the message dialog on top
if let Some(dialog) = &state.message_dialog {
render_message_dialog(f, dialog);
}

render_key_bindings(state, f, chunks[1]);
}

fn render_message_dialog(frame: &mut Frame, dialog: &MessageDialog) {
let (msg, style, title) = match dialog {
MessageDialog::Error(message) => (message, Style::default().fg(Color::Red), "Error"),
MessageDialog::Warning(message) => {
(message, Style::default().fg(Color::Yellow), "⚠ Warning")
}
MessageDialog::Info(message) => (message, Style::default(), "Info"),
};

let text = Text::styled(msg.clone(), style);
let area = frame.area();
let block = Block::bordered()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(style)
.title(title)
.style(style);
let area = popup_area_percent(area, 80, 10);
frame.render_widget(Clear, area); //this clears out the background
let paragraph = Paragraph::new(text).wrap(Wrap { trim: true }).block(block);
frame.render_widget(paragraph, area);
}

fn render_main(state: &mut State, f: &mut Frame, enabled: bool, area: Rect) {
let [list_filter_aea, info_area] = Layout::default()
.direction(Direction::Horizontal)
Expand Down Expand Up @@ -297,3 +325,16 @@ fn popup_area(area: Rect, width: u16, /*percent_x: u16,*/ height: u16) -> Rect {
let [area] = horizontal.areas(area);
area
}

fn popup_area_percent(area: Rect, percent_x: u16, max_height: u16) -> Rect {
let vertical = Layout::vertical([
Constraint::Length(2),
Constraint::Max(max_height),
Constraint::Length(3),
])
.flex(Flex::Center);
let horizontal = Layout::horizontal([Constraint::Percentage(percent_x)]).flex(Flex::Center);
let [_, area, _] = vertical.areas(area);
let [area] = horizontal.areas(area);
area
}
24 changes: 23 additions & 1 deletion src/frontend/update.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
use crate::frontend::state::State;
use crate::frontend::Action;
use crate::indexer::IndexedTable;
use crate::info_gather;
use crate::simplefrontend::TableOption;
use colored::Colorize;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::prelude::{Color, Text};
use ratatui::style::Style;
use ratatui::text::Span;
use ratatui::widgets::{ListItem, Paragraph};
use vpin::vpx::tableinfo::TableInfo;
use vpin::vpx::version::Version;

pub fn update(state: &mut State, key_event: KeyEvent) -> Action {
// always allow ctrl-c to quit
Expand All @@ -10,10 +19,23 @@ pub fn update(state: &mut State, key_event: KeyEvent) -> Action {
{
return Action::Quit;
}

// give priority to the message dialog
if let Some(_dialog) = &mut state.message_dialog {
return match key_event.code {
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Enter => {
state.message_dialog = None;
Action::None
}
_ => Action::None,
};
}

// handle the table dialog
match &mut state.table_dialog {
Some(dialog) => match key_event.code {
KeyCode::Esc | KeyCode::Char('q') => {
state.table_dialog = None;
state.close_dialog();
Action::None
}
KeyCode::Up => {
Expand Down
Loading

0 comments on commit 6fadbff

Please sign in to comment.