Compare commits

..

5 Commits

12 changed files with 488 additions and 268 deletions

81
crates/save/src/level.rs Normal file
View File

@ -0,0 +1,81 @@
use core::fmt;
use binrw::{BinRead, BinWrite};
use crate::{Error, Result};
pub const MAX_LEVEL_ID: u64 = 11;
pub const NAMES: [&str; MAX_LEVEL_ID as usize + 1] = [
"Chapter Select",
"Broken Bridge",
"Pink Desert",
"Sunken City",
"Underground",
"Tower",
"Snow",
"Paradise",
"Credits",
"Level Bryan",
"Level Matt",
"Level Chris",
];
#[derive(Debug, Clone, Copy, BinRead, BinWrite)]
pub struct Level {
#[br(assert(id <= MAX_LEVEL_ID))]
id: u64,
}
impl Level {
pub fn set_by_id(&mut self, id: u64) -> Result<()> {
if id > MAX_LEVEL_ID {
return Err(Error::LevelIdOutOfRange);
}
self.id = id;
Ok(())
}
pub fn set_by_name(&mut self, name: &str) -> Result<()> {
let id = NAMES
.iter()
.position(|&v| v == name)
.ok_or(Error::LevelNameNotFound)?;
self.id = id as u64;
Ok(())
}
pub fn wrapping_next(&self) -> Self {
let id = self.id + 1;
if id > MAX_LEVEL_ID {
Self { id: 0 }
} else {
Self { id }
}
}
pub fn wrapping_previous(&self) -> Self {
match self.id.checked_sub(1) {
Some(id) => Self { id },
None => Self { id: MAX_LEVEL_ID },
}
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", NAMES[self.id as usize])
}
}
impl AsRef<u64> for Level {
fn as_ref(&self) -> &u64 {
&self.id
}
}

View File

@ -1,38 +1,29 @@
mod companion;
mod glyphs;
mod level;
mod murals;
mod robe;
mod scarf;
mod symbol;
mod test;
use core::fmt;
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use binrw::{until_eof, BinRead, BinReaderExt, BinWriterExt};
use chrono::{DateTime, NaiveDateTime, Utc};
use level::Level;
use robe::Robe;
use scarf::Scarf;
use symbol::Symbol;
use crate::companion::{CompanionSymbols, CompanionWithId, Companions};
use crate::glyphs::Glyphs;
pub use crate::level::NAMES as LEVEL_NAMES;
use crate::murals::Murals;
pub const LEVEL_NAMES: [&str; 12] = [
"Chapter Select",
"Broken Bridge",
"Pink Desert",
"Sunken City",
"Underground",
"Tower",
"Snow",
"Paradise",
"Credits",
"Level Bryan",
"Level Matt",
"Level Chris",
];
pub use crate::robe::Color as RobeColor;
pub type Result<T, E = Error> = std::result::Result<T, E>;
@ -46,27 +37,32 @@ pub enum Error {
#[error("Failed to serialize savefile")]
SerializationFailed(binrw::Error),
#[error("Level id is out of range")]
LevelIdOutOfRange,
#[error("Level name was not found")]
LevelNameNotFound,
#[error("Scarf already at maximum length")]
ScarfMaxLength,
#[error("Scarf already at minimum length")]
ScarfMinLength,
#[error("Scarf can be at most 30 long")]
ScarfTooLong,
#[error("Symbol id is out of range")]
SymbolIdOutOfRange,
#[error(transparent)]
RobeChange(robe::Error),
#[error("Failed to read file")]
FileReadingFailed(io::Error),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RobeColor {
Red,
White,
}
impl fmt::Display for RobeColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Red => write!(f, "Red"),
Self::White => write!(f, "White"),
}
}
}
#[binrw::binrw]
#[derive(Debug, Clone)]
#[brw(little)]
@ -77,17 +73,16 @@ pub struct Savefile {
#[br(count = 8)]
_unknown0: Vec<u8>,
robe: u32,
pub robe: Robe,
pub symbol: Symbol,
pub scarf_length: u32,
pub scarf_length: Scarf,
#[br(count = 4)]
_unknown1: Vec<u8>,
#[br(assert(current_level <= 12))]
pub current_level: u64,
pub current_level: Level,
pub total_collected_symbols: u32,
@ -186,46 +181,6 @@ impl Savefile {
}
})
}
pub fn current_level_name(&self) -> &'static str {
LEVEL_NAMES[self.current_level as usize]
}
pub fn robe_color(&self) -> RobeColor {
if self.robe > 3 {
RobeColor::White
} else {
RobeColor::Red
}
}
pub fn set_robe_color(&mut self, color: RobeColor) {
self.robe = match (self.robe_color(), color) {
(RobeColor::Red, RobeColor::White) => self.robe + 4,
(RobeColor::White, RobeColor::Red) => self.robe - 4,
_ => return,
}
}
pub fn robe_tier(&self) -> u32 {
match self.robe_color() {
RobeColor::Red => self.robe + 1,
RobeColor::White => self.robe - 2,
}
}
pub fn set_robe_tier(&mut self, tier: u32) {
if tier < 1 || tier > 4 {
return;
}
self.robe = match self.robe_color() {
RobeColor::Red => tier - 1,
// There can't be a tier 1 white robe, setting it to the lowers possible tier
RobeColor::White if tier == 1 => 4,
RobeColor::White => tier + 2,
}
}
}
#[binrw::parser(reader, endian)]

192
crates/save/src/robe.rs Normal file
View File

@ -0,0 +1,192 @@
use core::fmt;
use std::str::FromStr;
use binrw::{BinRead, BinWrite};
use crate::Result;
const MIN_TIER: u32 = 1;
const MAX_TIER: u32 = 4;
const MAX_RED_TIER_ID: u32 = 3;
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[error("Tier must be in range from 1 to 4")]
TierOutOfRange,
#[error("White tier can not be lower than 2")]
WhiteTierMinimum,
#[error("Invalid color, expected red or white")]
InvalidColor,
}
#[derive(Debug, Clone, BinRead, BinWrite)]
pub struct Robe {
value: u32,
}
impl Robe {
pub fn color(&self) -> Color {
if self.value > MAX_RED_TIER_ID {
Color::White
} else {
Color::Red
}
}
pub fn set_color(&mut self, color: Color) {
self.value = match (self.color(), color, self.value) {
(Color::Red, Color::White, 0) => MAX_RED_TIER_ID + 1,
(Color::Red, Color::White, val) => val + MAX_RED_TIER_ID,
(Color::White, Color::Red, val) => val - MAX_RED_TIER_ID,
_ => return,
}
}
pub fn swap_colors(&mut self) {
let new_color = match self.color() {
Color::Red => Color::White,
Color::White => Color::Red,
};
self.set_color(new_color);
}
pub fn tier(&self) -> u32 {
match self.color() {
Color::Red => self.value + 1,
Color::White => self.value - MAX_RED_TIER_ID + 1,
}
}
pub fn set_tier(&mut self, tier: u32) -> Result<(), Error> {
if tier < MIN_TIER || tier > MAX_TIER {
return Err(Error::TierOutOfRange);
}
self.value = match self.color() {
Color::Red => tier - 1,
Color::White if tier == MIN_TIER => {
return Err(Error::WhiteTierMinimum);
}
Color::White => MAX_RED_TIER_ID + tier - 1,
};
Ok(())
}
pub fn increase_tier(&mut self) {
let _ = self.set_tier(self.tier() + 1);
}
pub fn decrease_tier(&mut self) {
let _ = self.set_tier(self.tier() - 1);
}
}
impl fmt::Display for Robe {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
Red,
White,
}
impl FromStr for Color {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Red" | "red" => Ok(Self::Red),
"White" | "white" => Ok(Self::White),
_ => Err(Error::InvalidColor),
}
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Red => write!(f, "Red"),
Self::White => write!(f, "White"),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn change_robe_color() {
// lowest red tier
let mut robe = Robe { value: 0 };
assert_eq!(robe.color(), Color::Red);
assert_eq!(robe.tier(), 1);
robe.set_color(Color::White);
assert_eq!(robe.color(), Color::White);
assert_eq!(robe.tier(), 2);
robe.set_color(Color::Red);
assert_eq!(robe.color(), Color::Red);
assert_eq!(robe.tier(), 2);
// highest red tier
let mut robe = Robe {
value: MAX_RED_TIER_ID,
};
assert_eq!(robe.color(), Color::Red);
assert_eq!(robe.tier(), 4);
robe.set_color(Color::White);
assert_eq!(robe.color(), Color::White);
assert_eq!(robe.tier(), 4);
robe.set_color(Color::Red);
assert_eq!(robe.color(), Color::Red);
assert_eq!(robe.tier(), 4);
}
#[test]
fn change_red_robe_tier() {
let mut robe = Robe { value: 0 };
for tier in 1..=4 {
robe.set_tier(tier).unwrap();
assert_eq!(robe.tier(), tier, "unexpected tier");
assert_eq!(robe.color(), Color::Red, "unexpected color");
}
}
#[test]
fn change_white_robe_tier() {
let mut robe = Robe { value: 4 };
let result = robe.set_tier(1);
assert_eq!(result, Err(Error::WhiteTierMinimum));
for (tier, expected) in (2..=4).zip([2, 3, 4]) {
robe.set_tier(tier).unwrap();
assert_eq!(robe.tier(), expected, "unexpected tier");
assert_eq!(robe.color(), Color::White, "unexpected color");
}
}
}

58
crates/save/src/scarf.rs Normal file
View File

@ -0,0 +1,58 @@
use core::fmt;
use binrw::{BinRead, BinWrite};
use crate::{Error, Result};
const MAX_LENGTH: u32 = 30;
#[derive(Debug, Clone, BinRead, BinWrite)]
pub struct Scarf {
#[br(assert(length <= MAX_LENGTH))]
length: u32,
}
impl Scarf {
pub fn set_length(&mut self, length: u32) -> Result<()> {
if length > MAX_LENGTH {
return Err(Error::ScarfTooLong);
}
self.length = length;
Ok(())
}
pub fn increase_length(&mut self) -> Result<()> {
let length = self.length + 1;
if length > MAX_LENGTH {
return Err(Error::ScarfMaxLength);
}
self.length = length;
Ok(())
}
pub fn decrease_length(&mut self) -> Result<()> {
let length = self.length.checked_sub(1).ok_or(Error::ScarfMinLength)?;
self.length = length;
Ok(())
}
}
impl fmt::Display for Scarf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.length)
}
}
impl AsRef<u32> for Scarf {
fn as_ref(&self) -> &u32 {
&self.length
}
}

View File

@ -3,7 +3,10 @@ use core::fmt;
use binrw::{BinRead, BinWrite};
use substring::Substring;
use crate::{Error, Result};
const MAX_SYMBOL_ID: u32 = 20;
const SYMBOL_PARTS: &str = include_str!("symbol_parts.txt");
const SYMBOL_PART_WIDTH: usize = 6;
const SYMBOL_PART_HEIGTH: usize = 3;
@ -11,7 +14,42 @@ const SYMBOL_PART_HEIGTH: usize = 3;
#[derive(Debug, Clone, Copy, BinRead, BinWrite)]
pub struct Symbol {
pub id: u32,
#[br(assert(id < MAX_SYMBOL_ID))]
id: u32,
}
impl Symbol {
pub fn set_by_id(&mut self, id: u32) -> Result<()> {
if id > MAX_SYMBOL_ID {
return Err(Error::SymbolIdOutOfRange);
}
self.id = id;
Ok(())
}
pub fn wrapping_next(&self) -> Self {
let id = self.id + 1;
if id > MAX_SYMBOL_ID {
Self { id: 0 }
} else {
Self { id }
}
}
pub fn wrapping_previous(&self) -> Self {
match self.id.checked_sub(1) {
Some(id) => Self { id },
None => Self { id: MAX_SYMBOL_ID },
}
}
}
impl AsRef<u32> for Symbol {
fn as_ref(&self) -> &u32 {
&self.id
}
}
impl fmt::Display for Symbol {

View File

@ -12,13 +12,12 @@ use crate::*;
fn general_info() {
let savefile = savefile();
assert_eq!(savefile.robe, 3);
assert_eq!(savefile.robe_color(), RobeColor::Red);
assert_eq!(savefile.robe_tier(), 4);
assert_eq!(savefile.robe.color(), RobeColor::Red);
assert_eq!(savefile.robe.tier(), 4);
assert_eq!(savefile.symbol.id, 7);
assert_eq!(savefile.scarf_length, 27);
assert_eq!(savefile.current_level, 1);
assert_eq!(savefile.symbol.as_ref(), &7);
assert_eq!(savefile.scarf_length.as_ref(), &27);
assert_eq!(savefile.current_level.as_ref(), &1);
assert_eq!(savefile.total_collected_symbols, 107);
assert_eq!(savefile.collected_symbols, 21);
assert_eq!(savefile.journey_count, 21);
@ -149,58 +148,6 @@ fn murals() {
}
#[test]
fn change_robe_color() {
let mut savefile = savefile();
// lowest tier
savefile.robe = 0;
savefile.set_robe_color(RobeColor::White);
assert_eq!(savefile.robe_color(), RobeColor::White);
savefile.set_robe_color(RobeColor::Red);
assert_eq!(savefile.robe_color(), RobeColor::Red);
// highest tier
savefile.robe = 3;
savefile.set_robe_color(RobeColor::White);
assert_eq!(savefile.robe_color(), RobeColor::White);
savefile.set_robe_color(RobeColor::Red);
assert_eq!(savefile.robe_color(), RobeColor::Red);
}
#[test]
fn change_robe_tier() {
let mut savefile = savefile();
savefile.set_robe_tier(1);
assert_eq!(savefile.robe_tier(), 1);
assert_eq!(savefile.robe_color(), RobeColor::Red);
savefile.set_robe_tier(4);
assert_eq!(savefile.robe_tier(), 4);
assert_eq!(savefile.robe_color(), RobeColor::Red);
savefile.set_robe_color(RobeColor::White);
savefile.set_robe_tier(2);
assert_eq!(savefile.robe_tier(), 2);
assert_eq!(savefile.robe_color(), RobeColor::White);
savefile.set_robe_tier(1);
assert_eq!(savefile.robe_tier(), 2);
assert_eq!(savefile.robe_color(), RobeColor::White);
savefile.set_robe_tier(4);
assert_eq!(savefile.robe_tier(), 4);
assert_eq!(savefile.robe_color(), RobeColor::White);
}
fn savefile() -> Savefile {
const TEST_FILE: &[u8] = include_bytes!("../test.bin");
let mut savefile = Cursor::new(TEST_FILE);

View File

@ -55,27 +55,27 @@ fn edit_file(cur_savefile: &Savefile, args: &Args) -> Result<Savefile> {
let mut savefile = cur_savefile.clone();
if let Some(val) = args.scarf_length {
savefile.scarf_length = val;
savefile.scarf_length.set_length(val)?;
}
if let Some(val) = &args.current_level {
savefile.current_level = LEVEL_NAMES.iter().position(|&v| v == val).unwrap() as u64;
savefile.current_level.set_by_name(&val)?;
}
if let Some(val) = args.symbol {
savefile.symbol.id = val;
savefile.symbol.set_by_id(val)?;
}
if let Some(color) = &args.robe_color {
match color.as_ref() {
"red" => savefile.set_robe_color(RobeColor::Red),
"white" => savefile.set_robe_color(RobeColor::White),
"red" => savefile.robe.set_color(RobeColor::Red),
"white" => savefile.robe.set_color(RobeColor::White),
_ => (),
}
}
if let Some(tier) = args.robe_tier {
savefile.set_robe_tier(tier);
savefile.robe.set_tier(tier)?;
}
Ok(savefile)

View File

@ -17,7 +17,7 @@ use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use ratatui::backend::CrosstermBackend;
use tracing::{debug, error, info};
use tracing::{debug, info};
use self::state::{Mode, State};
@ -104,6 +104,8 @@ fn run(terminal: &mut Terminal, mut state: State) -> Result<()> {
events::handle(&mut msg_tx, &mut state)?;
state.clear_expired_error_message();
match msg_rx.try_recv() {
Ok(Message::Exit) => {
debug!("Exiting...");
@ -111,8 +113,7 @@ fn run(terminal: &mut Terminal, mut state: State) -> Result<()> {
}
Ok(message) => {
if let Err(err) = handle_message(&mut state, &mut msg_tx, message) {
error!(message = ?err);
state.mode = Mode::ShowError(format!("{}", err));
state.show_error_message(err);
}
}
Err(TryRecvError::Empty) => (),
@ -174,18 +175,18 @@ fn handle_message(
Message::StartEditEntry => state.start_editing_entry(),
Message::CommitEditEntry => {
if let Err(err) = state.commit_entry_edit() {
error!(%err);
state.show_error_message(err);
}
}
Message::CancelEditEntry => state.cancel_editing_entry(),
Message::NextEntryValue => {
if let Err(err) = state.next_entry_value() {
error!(%err);
state.show_error_message(err);
}
}
Message::PreviousEntryValue => {
if let Err(err) = state.previous_entry_value() {
error!(%err);
state.show_error_message(err);
}
}
_ => (),

View File

@ -57,10 +57,6 @@ fn handle_keyboard_input(
msg_tx.send(Message::SetMode(Mode::Normal))?;
}
(Mode::ShowError(_), _) => {
msg_tx.send(Message::SetMode(Mode::Normal))?;
}
(Mode::SelectFile, KeyCode::Enter) => {
msg_tx.send(Message::LoadFile)?;
}
@ -147,5 +143,7 @@ fn handle_keyboard_input(
_ => (),
};
state.clear_error_message();
Ok(())
}

View File

@ -1,12 +1,14 @@
use core::fmt;
use std::fs::{self, create_dir_all, read_to_string};
use std::io::Write;
use std::os::unix::prelude::OsStrExt;
use std::path::Path;
use std::time::{Duration, Instant};
use anyhow::{bail, Context, Result};
use jrny_save::{RobeColor, Savefile, LEVEL_NAMES};
use jrny_save::Savefile;
use ratatui::widgets::TableState;
use tracing::debug;
use tracing::{debug, error};
use tui_input::Input;
use super::view::info::glyphs::TABLE_RANGE as GLYPHS_TABLE_RANGE;
@ -27,8 +29,6 @@ pub enum Mode {
Insert,
ShowError(String),
SelectFile,
}
@ -57,6 +57,7 @@ pub struct State {
pub stats_table: TableState,
pub glyphs_table: TableState,
pub murals_table: TableState,
pub error_msg: Option<(Instant, String)>,
pub mode: Mode,
pub edit_input: Option<Input>,
pub file_select: Input,
@ -66,6 +67,29 @@ pub struct State {
impl State {
const ERROR_MSG_DURATION: Duration = Duration::new(3, 0);
pub fn show_error_message<S>(&mut self, msg: S)
where
S: fmt::Display,
{
let until = Instant::now() + Self::ERROR_MSG_DURATION;
error!(%msg);
self.error_msg = Some((until, msg.to_string()));
}
pub fn clear_expired_error_message(&mut self) {
if let Some((until, _)) = self.error_msg {
if Instant::now() >= until {
self.error_msg = None;
}
}
}
pub fn clear_error_message(&mut self) {
self.error_msg.take();
}
pub fn load() -> Result<Self> {
let data_dir = DIRS.data_local_dir();
@ -147,40 +171,12 @@ impl State {
0 => savefile.journey_count = value.parse()?,
1 => savefile.total_companions_met = value.parse()?,
2 => savefile.total_collected_symbols = value.parse()?,
3 => {
let level_id = LEVEL_NAMES
.iter()
.position(|&v| v == value.trim_end())
.context("invalid level name")?;
savefile.current_level = level_id as u64;
}
3 => savefile.current_level.set_by_name(value)?,
4 => savefile.companions_met = value.parse()?,
5 => {
let new_length = value.parse()?;
if new_length > 30 {
bail!("Max length exceeded");
}
savefile.scarf_length = new_length;
}
6 => {
let next_symbol_id = value.parse()?;
if next_symbol_id > 20 {
bail!("Symbol id out of range");
}
savefile.symbol.id = next_symbol_id;
}
7 => {
let new_color = match value {
"Red" | "red" => RobeColor::Red,
"White" | "white" => RobeColor::White,
_ => bail!("invalid robe color"),
};
savefile.set_robe_color(new_color);
}
8 => savefile.set_robe_tier(value.parse()?),
5 => savefile.scarf_length.set_length(value.parse()?)?,
6 => savefile.symbol.set_by_id(value.parse()?)?,
7 => savefile.robe.set_color(value.parse()?),
8 => savefile.robe.set_tier(value.parse()?)?,
9 => {}
idx => debug!("unknown index {:?}", idx),
}
@ -206,38 +202,12 @@ impl State {
0 => savefile.journey_count += 1,
1 => savefile.total_companions_met += 1,
2 => savefile.total_collected_symbols += 1,
3 => {
let next_level = savefile.current_level + 1;
savefile.current_level = if next_level >= LEVEL_NAMES.len() as u64 {
0
} else {
savefile.current_level + 1
};
}
3 => savefile.current_level = savefile.current_level.wrapping_next(),
4 => savefile.companions_met += 1,
5 => {
if savefile.scarf_length < 30 {
savefile.scarf_length += 1;
}
}
6 => {
let next_symbol = savefile.symbol.id + 1;
savefile.symbol.id = if next_symbol > 20 {
0
} else {
savefile.symbol.id + 1
};
}
7 => {
savefile.set_robe_color(match savefile.robe_color() {
RobeColor::Red => RobeColor::White,
RobeColor::White => RobeColor::Red,
});
}
8 => {
let next_tier = savefile.robe_tier() + 1;
savefile.set_robe_tier(next_tier);
}
5 => savefile.scarf_length.increase_length()?,
6 => savefile.symbol = savefile.symbol.wrapping_next(),
7 => savefile.robe.swap_colors(),
8 => savefile.robe.increase_tier(),
9 => {}
idx => debug!("unknown index {:?}", idx),
}
@ -266,38 +236,12 @@ impl State {
savefile.total_collected_symbols =
savefile.total_collected_symbols.saturating_sub(1)
}
3 => {
let next_level: i64 = savefile.current_level as i64 - 1;
savefile.current_level = if next_level < 0 {
LEVEL_NAMES.len() as u64 - 1
} else {
next_level as u64
};
}
3 => savefile.current_level = savefile.current_level.wrapping_previous(),
4 => savefile.companions_met = savefile.companions_met.saturating_sub(1),
5 => {
if savefile.scarf_length > 0 {
savefile.scarf_length = savefile.scarf_length.saturating_sub(1);
}
}
6 => {
let next_symbol = savefile.symbol.id as i32 - 1;
savefile.symbol.id = if next_symbol < 0 {
20
} else {
next_symbol as u32
};
}
7 => {
savefile.set_robe_color(match savefile.robe_color() {
RobeColor::Red => RobeColor::White,
RobeColor::White => RobeColor::Red,
});
}
8 => {
let next_tier = savefile.robe_tier().saturating_sub(1);
savefile.set_robe_tier(next_tier);
}
5 => savefile.scarf_length.decrease_length()?,
6 => savefile.symbol = savefile.symbol.wrapping_previous(),
7 => savefile.robe.swap_colors(),
8 => savefile.robe.decrease_tier(),
9 => {}
idx => debug!("unknown index {:?}", idx),
}

View File

@ -52,12 +52,12 @@ pub(super) fn render<'a>(state: &mut State, frame: &mut Frame, area: Rect) {
"Total Symbols Collected",
savefile.total_collected_symbols.to_string(),
),
("Current Level", savefile.current_level_name().to_string()),
("Current Level", savefile.current_level.to_string()),
("Companions Met", savefile.companions_met.to_string()),
("Scarf Length", savefile.scarf_length.to_string()),
("Symbol Number", savefile.symbol.id.to_string()),
("Robe Color", savefile.robe_color().to_string()),
("Robe Tier", savefile.robe_tier().to_string()),
("Symbol Number", savefile.symbol.as_ref().to_string()),
("Robe Color", savefile.robe.color().to_string()),
("Robe Tier", savefile.robe.tier().to_string()),
("Last Played", savefile.last_played.to_string()),
]
.into_iter()

View File

@ -6,21 +6,28 @@ use crate::tui::view::Frame;
use crate::tui::{Mode, State};
pub fn render(state: &State, mut frame: &mut Frame, area: Rect) {
pub fn render(state: &State, frame: &mut Frame, area: Rect) {
let status_block = Block::default().padding(Padding::horizontal(2));
match &state.mode {
Mode::ShowError(error_msg) => {
let error_msg = Paragraph::new(error_msg.clone())
.style(Style::default().fg(ratatui::style::Color::LightRed))
.block(status_block);
frame.render_widget(error_msg, area);
}
match &state.error_msg {
Some((_, msg)) => render_error_message(&msg, frame, status_block, area),
None => render_status(state, frame, status_block, area),
}
}
fn render_error_message(msg: &str, frame: &mut Frame, block: Block, area: Rect) {
let error_msg = Paragraph::new(msg)
.style(Style::default().fg(ratatui::style::Color::LightRed))
.block(block);
frame.render_widget(error_msg, area);
}
pub fn render_status(state: &State, mut frame: &mut Frame, block: Block, area: Rect) {
match &state.mode {
Mode::Edit | Mode::Insert => {
if let Some(savefile) = &state.savefile {
let text = format!("Editing file: {}", savefile.path.display());
let status = Paragraph::new(text).block(status_block);
let status = Paragraph::new(text).block(block);
frame.render_widget(status, area);
}
}
@ -29,24 +36,23 @@ pub fn render(state: &State, mut frame: &mut Frame, area: Rect) {
Mode::Normal if state.is_watching_file() => {
if let Some(savefile) = &state.savefile {
let text = format!("Watching file: {}", savefile.path.display());
let status = Paragraph::new(text).block(status_block);
let status = Paragraph::new(text).block(block);
frame.render_widget(status, area);
}
}
Mode::SelectFile => render_file_select(&state, &mut frame, status_block, area),
Mode::SelectFile => render_file_select(&state, &mut frame, block, area),
_ => {
if let Some(savefile) = &state.savefile {
let text = format!("Showing file: {}", savefile.path.display());
let status = Paragraph::new(text).block(status_block);
let status = Paragraph::new(text).block(block);
frame.render_widget(status, area);
}
}
}
}
fn render_file_select(state: &State, frame: &mut Frame, block: Block, area: Rect) {
const PROMPT: &str = "Open file:";
const PADDING: usize = 2;