From 10ebff5a6fad37b366e5d6bbb189340645ce2cf0 Mon Sep 17 00:00:00 2001 From: Austin Traver <austintraver@gmail.com> Date: Tue, 15 Aug 2023 19:37:35 -0700 Subject: [PATCH 1/4] Refactor string representation for buttons and menus (#592) * Refactor string representation for buttons and menus * Pass test cases * Update help text --- src/common/menu.rs | 3 +- src/lib.rs | 39 +-- src/training/ui/menu.rs | 10 +- training_mod_consts/src/lib.rs | 544 +++++++++++++++-------------- training_mod_consts/src/options.rs | 106 ++++++ training_mod_tui/src/lib.rs | 107 +++--- training_mod_tui/src/main.rs | 10 +- 7 files changed, 462 insertions(+), 357 deletions(-) diff --git a/src/common/menu.rs b/src/common/menu.rs index c689629..023f76e 100644 --- a/src/common/menu.rs +++ b/src/common/menu.rs @@ -5,7 +5,6 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use skyline::nn::hid::GetNpadStyleSet; use training_mod_consts::MenuJsonStruct; - use training_mod_tui::AppPage; use crate::common::button_config::button_mapping; @@ -95,7 +94,7 @@ enum DirectionButton { } lazy_static! { - pub static ref QUICK_MENU_APP: Mutex<training_mod_tui::App<'static>> = Mutex::new( + pub static ref QUICK_MENU_APP: Mutex<training_mod_tui::App> = Mutex::new( training_mod_tui::App::new(unsafe { ui_menu(MENU) }, unsafe { ( ui_menu(DEFAULTS_MENU), diff --git a/src/lib.rs b/src/lib.rs index a3bf0df..f6f77fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,54 +106,25 @@ pub fn main() { unsafe { notification("Training Modpack".to_string(), "Welcome!".to_string(), 60); - notification( - "Open Menu".to_string(), - MENU.menu_open - .to_vec() - .iter() - .map(|button| button.as_str().unwrap()) - .intersperse(" + ") - .collect(), - 120, - ); + notification("Open Menu".to_string(), MENU.menu_open.to_string(), 120); notification( "Save State".to_string(), - MENU.save_state_save - .to_vec() - .iter() - .map(|button| button.as_str().unwrap()) - .intersperse(" + ") - .collect(), + MENU.save_state_save.to_string(), 120, ); notification( "Load State".to_string(), - MENU.save_state_load - .to_vec() - .iter() - .map(|button| button.as_str().unwrap()) - .intersperse(" + ") - .collect(), + MENU.save_state_load.to_string(), 120, ); notification( "Input Record".to_string(), - MENU.input_record - .to_vec() - .iter() - .map(|button| button.as_str().unwrap()) - .intersperse(" + ") - .collect(), + MENU.input_record.to_string(), 120, ); notification( "Input Playback".to_string(), - MENU.input_playback - .to_vec() - .iter() - .map(|button| button.as_str().unwrap()) - .intersperse(" + ") - .collect(), + MENU.input_playback.to_string(), 120, ); } diff --git a/src/training/ui/menu.rs b/src/training/ui/menu.rs index 12b624c..2cb2191 100644 --- a/src/training/ui/menu.rs +++ b/src/training/ui/menu.rs @@ -105,7 +105,7 @@ unsafe fn render_submenu_page(app: &App, root_pane: &Pane) { let submenu = &list.items[list_idx]; let is_selected = list.state.selected().filter(|s| *s == list_idx).is_some(); - title_text.set_text_string(submenu.submenu_title); + title_text.set_text_string(submenu.submenu_title.as_str()); // In the actual 'layout.arc' file, every icon image is stacked // into a single container pane, with each image directly on top of another. @@ -128,7 +128,7 @@ unsafe fn render_submenu_page(app: &App, root_pane: &Pane) { .find_pane_by_name_recursive("FooterTxt") .unwrap() .as_textbox() - .set_text_string(submenu.help_text); + .set_text_string(submenu.help_text.as_str()); title_bg_material.set_white_res_color(BG_LEFT_ON_WHITE_COLOR); title_bg_material.set_black_res_color(BG_LEFT_ON_BLACK_COLOR); @@ -250,7 +250,7 @@ unsafe fn render_slider_page(app: &App, root_pane: &Pane) { .find_pane_by_name_recursive("Header") .unwrap() .as_textbox(); - header.set_text_string(title); + header.set_text_string(title.as_str()); let min_button = slider_pane .find_pane_by_name_recursive("MinButton") .unwrap() @@ -411,7 +411,7 @@ pub unsafe fn draw(root_pane: &Pane) { } else { tab_selected + 1 }; - let tab_titles = [prev_tab, tab_selected, next_tab].map(|idx| app_tabs[idx]); + let tab_titles = [prev_tab, tab_selected, next_tab].map(|idx| app_tabs[idx].clone()); let is_gcc = (*common::menu::P1_CONTROLLER_STYLE.data_ptr()) == ControllerStyle::GCController; let button_mapping = if is_gcc { @@ -471,7 +471,7 @@ pub unsafe fn draw(root_pane: &Pane) { help_pane.set_default_material_colors(); help_pane.set_color(255, 255, 0, 255); } - help_pane.set_text_string(tab_titles[idx]); + help_pane.set_text_string(tab_titles[idx].as_str()); }); [ (save_defaults_key, "SaveDefaults", "Save Defaults"), diff --git a/training_mod_consts/src/lib.rs b/training_mod_consts/src/lib.rs index 77d6d1d..2fa9718 100644 --- a/training_mod_consts/src/lib.rs +++ b/training_mod_consts/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(iter_intersperse)] #[macro_use] extern crate bitflags; @@ -111,8 +112,8 @@ pub enum SubMenuType { } impl SubMenuType { - pub fn from_str(s: &str) -> SubMenuType { - match s { + pub fn from_string(s: &String) -> SubMenuType { + match s.as_str() { "toggle" => SubMenuType::TOGGLE, "slider" => SubMenuType::SLIDER, _ => panic!("Unexpected SubMenuType!"), @@ -203,25 +204,25 @@ pub struct Slider { } #[derive(Clone, Serialize)] -pub struct Toggle<'a> { +pub struct Toggle { pub toggle_value: u32, - pub toggle_title: &'a str, + pub toggle_title: String, pub checked: bool, } #[derive(Clone, Serialize)] -pub struct SubMenu<'a> { - pub submenu_title: &'a str, - pub submenu_id: &'a str, - pub help_text: &'a str, +pub struct SubMenu { + pub submenu_title: String, + pub submenu_id: String, + pub help_text: String, pub is_single_option: bool, - pub toggles: Vec<Toggle<'a>>, + pub toggles: Vec<Toggle>, pub slider: Option<Slider>, - pub _type: &'a str, + pub _type: String, } -impl<'a> SubMenu<'a> { - pub fn add_toggle(&mut self, toggle_value: u32, toggle_title: &'a str, checked: bool) { +impl SubMenu { + pub fn add_toggle(&mut self, toggle_value: u32, toggle_title: String, checked: bool) { self.toggles.push(Toggle { toggle_value, toggle_title, @@ -229,12 +230,12 @@ impl<'a> SubMenu<'a> { }); } pub fn new_with_toggles<T: ToggleTrait>( - submenu_title: &'a str, - submenu_id: &'a str, - help_text: &'a str, + submenu_title: String, + submenu_id: String, + help_text: String, is_single_option: bool, initial_value: &u32, - ) -> SubMenu<'a> { + ) -> SubMenu { let mut instance = SubMenu { submenu_title: submenu_title, submenu_id: submenu_id, @@ -242,15 +243,15 @@ impl<'a> SubMenu<'a> { is_single_option: is_single_option, toggles: Vec::new(), slider: None, - _type: "toggle", + _type: "toggle".to_string(), }; let values = T::to_toggle_vals(); - let titles = T::to_toggle_strs(); + let titles = T::to_toggle_strings(); for i in 0..values.len() { let checked: bool = (values[i] & initial_value) > 0 || (!values[i] == 0 && initial_value == &0); - instance.add_toggle(values[i], titles[i], checked); + instance.add_toggle(values[i], titles[i].clone(), checked); } // Select the first option if there's nothing selected atm but it's a single option submenu if is_single_option && instance.toggles.iter().all(|t| !t.checked) { @@ -259,12 +260,12 @@ impl<'a> SubMenu<'a> { instance } pub fn new_with_slider<S: SliderTrait>( - submenu_title: &'a str, - submenu_id: &'a str, - help_text: &'a str, + submenu_title: String, + submenu_id: String, + help_text: String, initial_lower_value: &u32, initial_upper_value: &u32, - ) -> SubMenu<'a> { + ) -> SubMenu { let min_max = S::get_limits(); SubMenu { submenu_title: submenu_title, @@ -278,31 +279,31 @@ impl<'a> SubMenu<'a> { abs_min: min_max.0, abs_max: min_max.1, }), - _type: "slider", + _type: "slider".to_string(), } } } #[derive(Serialize, Clone)] -pub struct Tab<'a> { - pub tab_id: &'a str, - pub tab_title: &'a str, - pub tab_submenus: Vec<SubMenu<'a>>, +pub struct Tab { + pub tab_id: String, + pub tab_title: String, + pub tab_submenus: Vec<SubMenu>, } -impl<'a> Tab<'a> { +impl Tab { pub fn add_submenu_with_toggles<T: ToggleTrait>( &mut self, - submenu_title: &'a str, - submenu_id: &'a str, - help_text: &'a str, + submenu_title: String, + submenu_id: String, + help_text: String, is_single_option: bool, initial_value: &u32, ) { self.tab_submenus.push(SubMenu::new_with_toggles::<T>( - submenu_title, - submenu_id, - help_text, + submenu_title.to_string(), + submenu_id.to_string(), + help_text.to_string(), is_single_option, initial_value, )); @@ -310,16 +311,16 @@ impl<'a> Tab<'a> { pub fn add_submenu_with_slider<S: SliderTrait>( &mut self, - submenu_title: &'a str, - submenu_id: &'a str, - help_text: &'a str, + submenu_title: String, + submenu_id: String, + help_text: String, initial_lower_value: &u32, initial_upper_value: &u32, ) { self.tab_submenus.push(SubMenu::new_with_slider::<S>( - submenu_title, - submenu_id, - help_text, + submenu_title.to_string(), + submenu_id.to_string(), + help_text.to_string(), initial_lower_value, initial_upper_value, )) @@ -327,542 +328,555 @@ impl<'a> Tab<'a> { } #[derive(Serialize, Clone)] -pub struct UiMenu<'a> { - pub tabs: Vec<Tab<'a>>, +pub struct UiMenu { + pub tabs: Vec<Tab>, } -pub unsafe fn ui_menu(menu: TrainingModpackMenu) -> UiMenu<'static> { +pub unsafe fn ui_menu(menu: TrainingModpackMenu) -> UiMenu { let mut overall_menu = UiMenu { tabs: Vec::new() }; let mut mash_tab = Tab { - tab_id: "mash", - tab_title: "Mash Settings", + tab_id: "mash".to_string(), + tab_title: "Mash Settings".to_string(), tab_submenus: Vec::new(), }; mash_tab.add_submenu_with_toggles::<Action>( - "Mash Toggles", - "mash_state", - "Mash Toggles: Actions to be performed as soon as possible", + "Mash Toggles".to_string(), + "mash_state".to_string(), + "Mash Toggles: Actions to be performed as soon as possible".to_string(), false, &(menu.mash_state.bits()), ); mash_tab.add_submenu_with_toggles::<Action>( - "Followup Toggles", - "follow_up", - "Followup Toggles: Actions to be performed after a Mash option", + "Followup Toggles".to_string(), + "follow_up".to_string(), + "Followup Toggles: Actions to be performed after a Mash option".to_string(), false, &(menu.follow_up.bits()), ); mash_tab.add_submenu_with_toggles::<MashTrigger>( - "Mash Triggers", - "mash_triggers", - "Mash triggers: Configure what causes the CPU to perform a Mash option", + "Mash Triggers".to_string(), + "mash_triggers".to_string(), + "Mash triggers: Configure what causes the CPU to perform a Mash option".to_string(), false, &(menu.mash_triggers.bits()), ); mash_tab.add_submenu_with_toggles::<AttackAngle>( - "Attack Angle", - "attack_angle", - "Attack Angle: For attacks that can be angled, such as some forward tilts", + "Attack Angle".to_string(), + "attack_angle".to_string(), + "Attack Angle: For attacks that can be angled, such as some forward tilts".to_string(), false, &(menu.attack_angle.bits()), ); mash_tab.add_submenu_with_toggles::<ThrowOption>( - "Throw Options", - "throw_state", - "Throw Options: Throw to be performed when a grab is landed", + "Throw Options".to_string(), + "throw_state".to_string(), + "Throw Options: Throw to be performed when a grab is landed".to_string(), false, &(menu.throw_state.bits()), ); mash_tab.add_submenu_with_toggles::<MedDelay>( - "Throw Delay", - "throw_delay", - "Throw Delay: How many frames to delay the throw option", + "Throw Delay".to_string(), + "throw_delay".to_string(), + "Throw Delay: How many frames to delay the throw option".to_string(), false, &(menu.throw_delay.bits()), ); mash_tab.add_submenu_with_toggles::<MedDelay>( - "Pummel Delay", - "pummel_delay", - "Pummel Delay: How many frames after a grab to wait before starting to pummel", + "Pummel Delay".to_string(), + "pummel_delay".to_string(), + "Pummel Delay: How many frames after a grab to wait before starting to pummel".to_string(), false, &(menu.pummel_delay.bits()), ); mash_tab.add_submenu_with_toggles::<BoolFlag>( - "Falling Aerials", - "falling_aerials", - "Falling Aerials: Should aerials be performed when rising or when falling", + "Falling Aerials".to_string(), + "falling_aerials".to_string(), + "Falling Aerials: Should aerials be performed when rising or when falling".to_string(), false, &(menu.falling_aerials.bits()), ); mash_tab.add_submenu_with_toggles::<BoolFlag>( - "Full Hop", - "full_hop", - "Full Hop: Should the CPU perform a full hop or a short hop", + "Full Hop".to_string(), + "full_hop".to_string(), + "Full Hop: Should the CPU perform a full hop or a short hop".to_string(), false, &(menu.full_hop.bits()), ); mash_tab.add_submenu_with_toggles::<Delay>( - "Aerial Delay", - "aerial_delay", - "Aerial Delay: How long to delay a Mash aerial attack", + "Aerial Delay".to_string(), + "aerial_delay".to_string(), + "Aerial Delay: How long to delay a Mash aerial attack".to_string(), false, &(menu.aerial_delay.bits()), ); mash_tab.add_submenu_with_toggles::<BoolFlag>( - "Fast Fall", - "fast_fall", - "Fast Fall: Should the CPU fastfall during a jump", + "Fast Fall".to_string(), + "fast_fall".to_string(), + "Fast Fall: Should the CPU fastfall during a jump".to_string(), false, &(menu.fast_fall.bits()), ); mash_tab.add_submenu_with_toggles::<Delay>( - "Fast Fall Delay", - "fast_fall_delay", - "Fast Fall Delay: How many frames the CPU should delay their fastfall", + "Fast Fall Delay".to_string(), + "fast_fall_delay".to_string(), + "Fast Fall Delay: How many frames the CPU should delay their fastfall".to_string(), false, &(menu.fast_fall_delay.bits()), ); mash_tab.add_submenu_with_toggles::<Delay>( - "OoS Offset", - "oos_offset", - "OoS Offset: How many times the CPU shield can be hit before performing a Mash option", + "OoS Offset".to_string(), + "oos_offset".to_string(), + "OoS Offset: How many times the CPU shield can be hit before performing a Mash option" + .to_string(), false, &(menu.oos_offset.bits()), ); mash_tab.add_submenu_with_toggles::<Delay>( - "Reaction Time", - "reaction_time", - "Reaction Time: How many frames to delay before performing a mash option", + "Reaction Time".to_string(), + "reaction_time".to_string(), + "Reaction Time: How many frames to delay before performing a mash option".to_string(), false, &(menu.reaction_time.bits()), ); overall_menu.tabs.push(mash_tab); let mut override_tab = Tab { - tab_id: "override", - tab_title: "Override Settings", + tab_id: "override".to_string(), + tab_title: "Override Settings".to_string(), tab_submenus: Vec::new(), }; override_tab.add_submenu_with_toggles::<Action>( - "Ledge Neutral Getup", - "ledge_neutral_override", - "Neutral Getup Override: Mash Actions to be performed after a Neutral Getup from ledge", + "Ledge Neutral Getup".to_string(), + "ledge_neutral_override".to_string(), + "Neutral Getup Override: Mash Actions to be performed after a Neutral Getup from ledge" + .to_string(), false, &(menu.ledge_neutral_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Ledge Roll", - "ledge_roll_override", - "Ledge Roll Override: Mash Actions to be performed after a Roll Getup from ledge", + "Ledge Roll".to_string(), + "ledge_roll_override".to_string(), + "Ledge Roll Override: Mash Actions to be performed after a Roll Getup from ledge" + .to_string(), false, &(menu.ledge_roll_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Ledge Jump", - "ledge_jump_override", - "Ledge Jump Override: Mash Actions to be performed after a Jump Getup from ledge", + "Ledge Jump".to_string(), + "ledge_jump_override".to_string(), + "Ledge Jump Override: Mash Actions to be performed after a Jump Getup from ledge" + .to_string(), false, &(menu.ledge_jump_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Ledge Attack", - "ledge_attack_override", - "Ledge Attack Override: Mash Actions to be performed after a Getup Attack from ledge", + "Ledge Attack".to_string(), + "ledge_attack_override".to_string(), + "Ledge Attack Override: Mash Actions to be performed after a Getup Attack from ledge" + .to_string(), false, &(menu.ledge_attack_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Tech Action", - "tech_action_override", - "Tech Action Override: Mash Actions to be performed after any tech action", + "Tech Action".to_string(), + "tech_action_override".to_string(), + "Tech Action Override: Mash Actions to be performed after any tech action".to_string(), false, &(menu.tech_action_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Clatter", - "clatter_override", - "Clatter Override: Mash Actions to be performed after leaving a clatter situation (grab, bury, etc)", + "Clatter".to_string(), + "clatter_override".to_string(), + "Clatter Override: Mash Actions to be performed after leaving a clatter situation (grab.to_string(), bury, etc)".to_string(), false, &(menu.clatter_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Tumble", - "tumble_override", - "Tumble Override: Mash Actions to be performed after exiting a tumble state", + "Tumble".to_string(), + "tumble_override".to_string(), + "Tumble Override: Mash Actions to be performed after exiting a tumble state".to_string(), false, &(menu.tumble_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Hitstun", - "hitstun_override", - "Hitstun Override: Mash Actions to be performed after exiting a hitstun state", + "Hitstun".to_string(), + "hitstun_override".to_string(), + "Hitstun Override: Mash Actions to be performed after exiting a hitstun state".to_string(), false, &(menu.hitstun_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Parry", - "parry_override", - "Parry Override: Mash Actions to be performed after a parry", + "Parry".to_string(), + "parry_override".to_string(), + "Parry Override: Mash Actions to be performed after a parry".to_string(), false, &(menu.parry_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Shieldstun", - "shieldstun_override", - "Shieldstun Override: Mash Actions to be performed after exiting a shieldstun state", + "Shieldstun".to_string(), + "shieldstun_override".to_string(), + "Shieldstun Override: Mash Actions to be performed after exiting a shieldstun state" + .to_string(), false, &(menu.shieldstun_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Footstool", - "footstool_override", - "Footstool Override: Mash Actions to be performed after exiting a footstool state", + "Footstool".to_string(), + "footstool_override".to_string(), + "Footstool Override: Mash Actions to be performed after exiting a footstool state" + .to_string(), false, &(menu.footstool_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Landing", - "landing_override", - "Landing Override: Mash Actions to be performed after landing on the ground", + "Landing".to_string(), + "landing_override".to_string(), + "Landing Override: Mash Actions to be performed after landing on the ground".to_string(), false, &(menu.landing_override.bits()), ); override_tab.add_submenu_with_toggles::<Action>( - "Ledge Trump", - "trump_override", - "Ledge Trump Override: Mash Actions to be performed after leaving a ledgetrump state", + "Ledge Trump".to_string(), + "trump_override".to_string(), + "Ledge Trump Override: Mash Actions to be performed after leaving a ledgetrump state" + .to_string(), false, &(menu.trump_override.bits()), ); overall_menu.tabs.push(override_tab); let mut defensive_tab = Tab { - tab_id: "defensive", - tab_title: "Defensive Settings", + tab_id: "defensive".to_string(), + tab_title: "Defensive Settings".to_string(), tab_submenus: Vec::new(), }; defensive_tab.add_submenu_with_toggles::<Direction>( - "Airdodge Direction", - "air_dodge_dir", - "Airdodge Direction: Direction to angle airdodges", + "Airdodge Direction".to_string(), + "air_dodge_dir".to_string(), + "Airdodge Direction: Direction to angle airdodges".to_string(), false, &(menu.air_dodge_dir.bits()), ); defensive_tab.add_submenu_with_toggles::<Direction>( - "DI Direction", - "di_state", - "DI Direction: Direction to angle the directional influence during hitlag", + "DI Direction".to_string(), + "di_state".to_string(), + "DI Direction: Direction to angle the directional influence during hitlag".to_string(), false, &(menu.di_state.bits()), ); defensive_tab.add_submenu_with_toggles::<Direction>( - "SDI Direction", - "sdi_state", - "SDI Direction: Direction to angle the smash directional influence during hitlag", + "SDI Direction".to_string(), + "sdi_state".to_string(), + "SDI Direction: Direction to angle the smash directional influence during hitlag" + .to_string(), false, &(menu.sdi_state.bits()), ); defensive_tab.add_submenu_with_toggles::<SdiFrequency>( - "SDI Strength", - "sdi_strength", - "SDI Strength: Relative strength of the smash directional influence inputs", + "SDI Strength".to_string(), + "sdi_strength".to_string(), + "SDI Strength: Relative strength of the smash directional influence inputs".to_string(), true, &(menu.sdi_strength as u32), ); defensive_tab.add_submenu_with_toggles::<ClatterFrequency>( - "Clatter Strength", - "clatter_strength", - "Clatter Strength: Configure how rapidly the CPU will mash out of grabs, buries, etc.", + "Clatter Strength".to_string(), + "clatter_strength".to_string(), + "Clatter Strength: Configure how rapidly the CPU will mash out of grabs, buries, etc." + .to_string(), true, &(menu.clatter_strength as u32), ); defensive_tab.add_submenu_with_toggles::<LedgeOption>( - "Ledge Options", - "ledge_state", - "Ledge Options: Actions to be taken when on the ledge", + "Ledge Options".to_string(), + "ledge_state".to_string(), + "Ledge Options: Actions to be taken when on the ledge".to_string(), false, &(menu.ledge_state.bits()), ); defensive_tab.add_submenu_with_toggles::<LongDelay>( - "Ledge Delay", - "ledge_delay", - "Ledge Delay: How many frames to delay the ledge option", + "Ledge Delay".to_string(), + "ledge_delay".to_string(), + "Ledge Delay: How many frames to delay the ledge option".to_string(), false, &(menu.ledge_delay.bits()), ); defensive_tab.add_submenu_with_toggles::<TechFlags>( - "Tech Options", - "tech_state", - "Tech Options: Actions to take when slammed into a hard surface", + "Tech Options".to_string(), + "tech_state".to_string(), + "Tech Options: Actions to take when slammed into a hard surface".to_string(), false, &(menu.tech_state.bits()), ); defensive_tab.add_submenu_with_toggles::<MissTechFlags>( - "Mistech Options", - "miss_tech_state", - "Mistech Options: Actions to take after missing a tech", + "Mistech Options".to_string(), + "miss_tech_state".to_string(), + "Mistech Options: Actions to take after missing a tech".to_string(), false, &(menu.miss_tech_state.bits()), ); defensive_tab.add_submenu_with_toggles::<Shield>( - "Shield Toggles", - "shield_state", - "Shield Toggles: CPU Shield Behavior", + "Shield Toggles".to_string(), + "shield_state".to_string(), + "Shield Toggles: CPU Shield Behavior".to_string(), true, &(menu.shield_state as u32), ); defensive_tab.add_submenu_with_toggles::<Direction>( - "Shield Tilt", - "shield_tilt", - "Shield Tilt: Direction to tilt the shield", + "Shield Tilt".to_string(), + "shield_tilt".to_string(), + "Shield Tilt: Direction to tilt the shield".to_string(), false, // TODO: Should this be true? &(menu.shield_tilt.bits()), ); defensive_tab.add_submenu_with_toggles::<OnOff>( - "Crouch", - "crouch", - "Crouch: Have the CPU crouch when on the ground", + "Crouch".to_string(), + "crouch".to_string(), + "Crouch: Have the CPU crouch when on the ground".to_string(), true, &(menu.crouch as u32), ); overall_menu.tabs.push(defensive_tab); let mut save_state_tab = Tab { - tab_id: "save_state", - tab_title: "Save States", + tab_id: "save_state".to_string(), + tab_title: "Save States".to_string(), tab_submenus: Vec::new(), }; save_state_tab.add_submenu_with_toggles::<SaveStateMirroring>( - "Mirroring", - "save_state_mirroring", - "Mirroring: Flips save states in the left-right direction across the stage center", + "Mirroring".to_string(), + "save_state_mirroring".to_string(), + "Mirroring: Flips save states in the left-right direction across the stage center" + .to_string(), true, &(menu.save_state_mirroring as u32), ); save_state_tab.add_submenu_with_toggles::<OnOff>( - "Auto Save States", - "save_state_autoload", - "Auto Save States: Load save state when any fighter dies", + "Auto Save States".to_string(), + "save_state_autoload".to_string(), + "Auto Save States: Load save state when any fighter dies".to_string(), true, &(menu.save_state_autoload as u32), ); save_state_tab.add_submenu_with_toggles::<SaveDamage>( - "Save Dmg (CPU)", - "save_damage_cpu", - "Save Damage: Should save states retain CPU damage", + "Save Dmg (CPU)".to_string(), + "save_damage_cpu".to_string(), + "Save Damage: Should save states retain CPU damage".to_string(), true, &(menu.save_damage_cpu.bits()), ); save_state_tab.add_submenu_with_slider::<DamagePercent>( - "Dmg Range (CPU)", - "save_damage_limits_cpu", - "Limits on random damage to apply to the CPU when loading a save state", + "Dmg Range (CPU)".to_string(), + "save_damage_limits_cpu".to_string(), + "Limits on random damage to apply to the CPU when loading a save state".to_string(), &(menu.save_damage_limits_cpu.0 as u32), &(menu.save_damage_limits_cpu.1 as u32), ); save_state_tab.add_submenu_with_toggles::<SaveDamage>( - "Save Dmg (Player)", - "save_damage_player", - "Save Damage: Should save states retain player damage", + "Save Dmg (Player)".to_string(), + "save_damage_player".to_string(), + "Save Damage: Should save states retain player damage".to_string(), true, &(menu.save_damage_player.bits() as u32), ); save_state_tab.add_submenu_with_slider::<DamagePercent>( - "Dmg Range (Player)", - "save_damage_limits_player", - "Limits on random damage to apply to the player when loading a save state", + "Dmg Range (Player)".to_string(), + "save_damage_limits_player".to_string(), + "Limits on random damage to apply to the player when loading a save state".to_string(), &(menu.save_damage_limits_player.0 as u32), &(menu.save_damage_limits_player.1 as u32), ); save_state_tab.add_submenu_with_toggles::<OnOff>( - "Enable Save States", - "save_state_enable", - "Save States: Enable save states! Save a state with Shield+Down Taunt, load it with Shield+Up Taunt.", + "Enable Save States".to_string(), + "save_state_enable".to_string(), + "Save States: Enable save states! Save a state with Shield+Down Taunt, load it with Shield+Up Taunt.".to_string(), true, &(menu.save_state_enable as u32), ); save_state_tab.add_submenu_with_toggles::<SaveStateSlot>( - "Save State Slot", - "save_state_slot", - "Save State Slot: Save and load states from different slots.", + "Save State Slot".to_string(), + "save_state_slot".to_string(), + "Save State Slot: Save and load states from different slots.".to_string(), true, &(menu.save_state_slot as u32), ); save_state_tab.add_submenu_with_toggles::<OnOff>( - "Randomize Slots", - "randomize_slots", - "Randomize Slots: Randomize slot when loading save state.", + "Randomize Slots".to_string(), + "randomize_slots".to_string(), + "Randomize Slots: Randomize slot when loading save state.".to_string(), true, &(menu.randomize_slots as u32), ); save_state_tab.add_submenu_with_toggles::<CharacterItem>( - "Character Item", - "character_item", - "Character Item: The item to give to the player's fighter when loading a save state", + "Character Item".to_string(), + "character_item".to_string(), + "Character Item: The item to give to the player's fighter when loading a save state" + .to_string(), true, &(menu.character_item as u32), ); save_state_tab.add_submenu_with_toggles::<BuffOption>( - "Buff Options", - "buff_state", - "Buff Options: Buff(s) to be applied to the respective fighters when loading a save state", + "Buff Options".to_string(), + "buff_state".to_string(), + "Buff Options: Buff(s) to be applied to the respective fighters when loading a save state" + .to_string(), false, &(menu.buff_state.bits()), ); save_state_tab.add_submenu_with_toggles::<PlaybackSlot>( - "Save State Playback", - "save_state_playback", - "Save State Playback: Choose which slots to playback input recording upon loading a save state", + "Save State Playback".to_string(), + "save_state_playback".to_string(), + "Save State Playback: Choose which slots to playback input recording upon loading a save state".to_string(), false, &(menu.save_state_playback.bits() as u32), ); overall_menu.tabs.push(save_state_tab); let mut misc_tab = Tab { - tab_id: "misc", - tab_title: "Misc Settings", + tab_id: "misc".to_string(), + tab_title: "Misc Settings".to_string(), tab_submenus: Vec::new(), }; misc_tab.add_submenu_with_toggles::<OnOff>( - "Frame Advantage", - "frame_advantage", - "Frame Advantage: Display the time difference between when the player is actionable and the CPU is actionable", + "Frame Advantage".to_string(), + "frame_advantage".to_string(), + "Frame Advantage: Display the time difference between when the player is actionable and the CPU is actionable".to_string(), true, &(menu.frame_advantage as u32), ); misc_tab.add_submenu_with_toggles::<OnOff>( - "Hitbox Visualization", - "hitbox_vis", - "Hitbox Visualization: Display a visual representation for active hitboxes (hides other visual effects)", + "Hitbox Visualization".to_string(), + "hitbox_vis".to_string(), + "Hitbox Visualization: Display a visual representation for active hitboxes (hides other visual effects)".to_string(), true, &(menu.hitbox_vis as u32), ); misc_tab.add_submenu_with_toggles::<Delay>( - "Input Delay", - "input_delay", - "Input Delay: Frames to delay player inputs by", + "Input Delay".to_string(), + "input_delay".to_string(), + "Input Delay: Frames to delay player inputs by".to_string(), true, &(menu.input_delay.bits()), ); misc_tab.add_submenu_with_toggles::<OnOff>( - "Stage Hazards", - "stage_hazards", - "Stage Hazards: Turn stage hazards on/off", + "Stage Hazards".to_string(), + "stage_hazards".to_string(), + "Stage Hazards: Turn stage hazards on/off".to_string(), true, &(menu.stage_hazards as u32), ); misc_tab.add_submenu_with_toggles::<OnOff>( - "HUD", - "hud", - "HUD: Show/hide elements of the UI", + "HUD".to_string(), + "hud".to_string(), + "HUD: Show/hide elements of the UI".to_string(), true, &(menu.hud as u32), ); overall_menu.tabs.push(misc_tab); let mut input_tab = Tab { - tab_id: "input", - tab_title: "Input Recording", + tab_id: "input".to_string(), + tab_title: "Input Recording".to_string(), tab_submenus: Vec::new(), }; input_tab.add_submenu_with_toggles::<RecordSlot>( - "Recording Slot", - "recording_slot", - "Recording Slot: Choose which slot to record into", + "Recording Slot".to_string(), + "recording_slot".to_string(), + "Recording Slot: Choose which slot to record into".to_string(), true, &(menu.recording_slot as u32), ); input_tab.add_submenu_with_toggles::<RecordTrigger>( - "Recording Trigger", - "record_trigger", - "Recording Trigger: Whether to begin recording via button combination (Default: Attack+Left Taunt) or upon loading a Save State", + "Recording Trigger".to_string(), + "record_trigger".to_string(), + format!("Recording Trigger: Whether to begin recording via button combination ({}) or upon loading a Save State", menu.input_record.combination_string()), false, &(menu.record_trigger.bits() as u32), ); input_tab.add_submenu_with_toggles::<RecordingFrames>( - "Recording Frames", - "recording_frames", - "Recording Frames: Number of frames to record for in the current slot", + "Recording Frames".to_string(), + "recording_frames".to_string(), + "Recording Frames: Number of frames to record for in the current slot".to_string(), true, &(menu.recording_frames as u32), ); input_tab.add_submenu_with_toggles::<PlaybackSlot>( - "Playback Button Combination", - "playback_button_combination", - "Playback Button Combination: Choose which slots to playback input recording upon pressing button combination (Default: Attack+Right Taunt)", + "Playback Button Combination".to_string(), + "playback_button_combination".to_string(), + format!("Playback Button Combination: Choose which slots to playback input recording upon pressing button combination ({})", menu.input_playback.combination_string()), false, &(menu.playback_button_combination.bits() as u32), ); input_tab.add_submenu_with_toggles::<HitstunPlayback>( - "Playback Hitstun Timing", - "hitstun_playback", - "Playback Hitstun Timing: When to begin playing back inputs when a hitstun mash trigger occurs", + "Playback Hitstun Timing".to_string(), + "hitstun_playback".to_string(), + "Playback Hitstun Timing: When to begin playing back inputs when a hitstun mash trigger occurs".to_string(), true, &(menu.hitstun_playback as u32), ); input_tab.add_submenu_with_toggles::<OnOff>( - "Playback Mash Interrupt", - "playback_mash", - "Playback Mash Interrupt: End input playback when a mash trigger occurs", + "Playback Mash Interrupt".to_string(), + "playback_mash".to_string(), + "Playback Mash Interrupt: End input playback when a mash trigger occurs".to_string(), true, &(menu.playback_mash as u32), ); input_tab.add_submenu_with_toggles::<OnOff>( - "Playback Loop", - "playback_loop", - "Playback Loop: Repeat triggered input playbacks indefinitely", + "Playback Loop".to_string(), + "playback_loop".to_string(), + "Playback Loop: Repeat triggered input playbacks indefinitely".to_string(), true, &(menu.playback_loop as u32), ); input_tab.add_submenu_with_toggles::<OnOff>( - "Recording Crop", - "recording_crop", - "Recording Crop: Remove neutral input frames at the end of your recording", + "Recording Crop".to_string(), + "recording_crop".to_string(), + "Recording Crop: Remove neutral input frames at the end of your recording".to_string(), true, &(menu.recording_crop as u32), ); overall_menu.tabs.push(input_tab); let mut button_tab = Tab { - tab_id: "button", - tab_title: "Button Config", + tab_id: "button".to_string(), + tab_title: "Button Config".to_string(), tab_submenus: Vec::new(), }; button_tab.add_submenu_with_toggles::<ButtonConfig>( - "Menu Open", - "menu_open", - "Menu Open: Hold: Hold any one button and press the others to trigger", + "Menu Open".to_string(), + "menu_open".to_string(), + "Menu Open: Hold: Hold any one button and press the others to trigger".to_string(), false, &(menu.menu_open.bits() as u32), ); button_tab.add_submenu_with_toggles::<ButtonConfig>( - "Save State Save", - "save_state_save", - "Save State Save: Hold any one button and press the others to trigger", + "Save State Save".to_string(), + "save_state_save".to_string(), + "Save State Save: Hold any one button and press the others to trigger".to_string(), false, &(menu.save_state_save.bits() as u32), ); button_tab.add_submenu_with_toggles::<ButtonConfig>( - "Save State Load", - "save_state_load", - "Save State Load: Hold any one button and press the others to trigger", + "Save State Load".to_string(), + "save_state_load".to_string(), + "Save State Load: Hold any one button and press the others to trigger".to_string(), false, &(menu.save_state_load.bits() as u32), ); button_tab.add_submenu_with_toggles::<ButtonConfig>( - "Input Record", - "input_record", - "Input Record: Hold any one button and press the others to trigger", + "Input Record".to_string(), + "input_record".to_string(), + "Input Record: Hold any one button and press the others to trigger".to_string(), false, &(menu.input_record.bits() as u32), ); button_tab.add_submenu_with_toggles::<ButtonConfig>( - "Input Playback", - "input_playback", - "Input Playback: Hold any one button and press the others to trigger", + "Input Playback".to_string(), + "input_playback".to_string(), + "Input Playback: Hold any one button and press the others to trigger".to_string(), false, &(menu.input_playback.bits() as u32), ); diff --git a/training_mod_consts/src/options.rs b/training_mod_consts/src/options.rs index ce7df62..9b54a0f 100644 --- a/training_mod_consts/src/options.rs +++ b/training_mod_consts/src/options.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; #[cfg(feature = "smash")] use smash::lib::lua_const::*; +use std::fmt; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -21,6 +22,7 @@ fn log_2(x: u32) -> u32 { pub trait ToggleTrait { fn to_toggle_strs() -> Vec<&'static str>; fn to_toggle_vals() -> Vec<u32>; + fn to_toggle_strings() -> Vec<String>; } pub trait SliderTrait { @@ -81,6 +83,11 @@ macro_rules! extra_bitflag_impls { let all_options = <$e>::all().to_vec(); all_options.iter().map(|i| i.bits() as u32).collect() } + + fn to_toggle_strings() -> Vec<String> { + let all_options = <$e>::all().to_vec(); + all_options.iter().map(|i| i.to_string()).collect() + } } } } @@ -346,6 +353,12 @@ impl Shield { } } +impl fmt::Display for Shield { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str().unwrap()) + } +} + impl ToggleTrait for Shield { fn to_toggle_strs() -> Vec<&'static str> { Shield::iter().map(|i| i.as_str().unwrap_or("")).collect() @@ -354,6 +367,9 @@ impl ToggleTrait for Shield { fn to_toggle_vals() -> Vec<u32> { Shield::iter().map(|i| i as u32).collect() } + fn to_toggle_strings() -> Vec<String> { + Shield::iter().map(|i| i.to_string()).collect() + } } // Save State Mirroring @@ -377,6 +393,12 @@ impl SaveStateMirroring { } } +impl fmt::Display for SaveStateMirroring { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str().unwrap()) + } +} + impl ToggleTrait for SaveStateMirroring { fn to_toggle_strs() -> Vec<&'static str> { SaveStateMirroring::iter() @@ -387,6 +409,10 @@ impl ToggleTrait for SaveStateMirroring { fn to_toggle_vals() -> Vec<u32> { SaveStateMirroring::iter().map(|i| i as u32).collect() } + + fn to_toggle_strings() -> Vec<String> { + SaveStateMirroring::iter().map(|i| i.to_string()).collect() + } } #[repr(i32)] @@ -420,6 +446,9 @@ impl ToggleTrait for OnOff { fn to_toggle_vals() -> Vec<u32> { vec![0, 1] } + fn to_toggle_strings() -> Vec<String> { + vec!["Off".to_string(), "On".to_string()] + } } bitflags! { @@ -1006,6 +1035,16 @@ impl ToggleTrait for SdiFrequency { fn to_toggle_vals() -> Vec<u32> { SdiFrequency::iter().map(|i| i as u32).collect() } + + fn to_toggle_strings() -> Vec<String> { + SdiFrequency::iter().map(|i| i.to_string()).collect() + } +} + +impl fmt::Display for SdiFrequency { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str().unwrap()) + } } #[repr(u32)] @@ -1049,6 +1088,16 @@ impl ToggleTrait for ClatterFrequency { fn to_toggle_vals() -> Vec<u32> { ClatterFrequency::iter().map(|i| i as u32).collect() } + + fn to_toggle_strings() -> Vec<String> { + ClatterFrequency::iter().map(|i| i.to_string()).collect() + } +} + +impl fmt::Display for ClatterFrequency { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str().unwrap()) + } } /// Item Selections @@ -1114,6 +1163,16 @@ impl ToggleTrait for CharacterItem { fn to_toggle_vals() -> Vec<u32> { CharacterItem::iter().map(|i| i as u32).collect() } + + fn to_toggle_strings() -> Vec<String> { + CharacterItem::iter().map(|i| i.to_string()).collect() + } +} + +impl fmt::Display for CharacterItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str().unwrap()) + } } bitflags! { @@ -1251,6 +1310,16 @@ impl ToggleTrait for SaveStateSlot { fn to_toggle_vals() -> Vec<u32> { SaveStateSlot::iter().map(|i| i as u32).collect() } + + fn to_toggle_strings() -> Vec<String> { + SaveStateSlot::iter().map(|i| i.to_string()).collect() + } +} + +impl fmt::Display for SaveStateSlot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str().unwrap()) + } } // Input Recording Slot @@ -1298,6 +1367,16 @@ impl ToggleTrait for RecordSlot { fn to_toggle_vals() -> Vec<u32> { RecordSlot::iter().map(|i| i as u32).collect() } + + fn to_toggle_strings() -> Vec<String> { + RecordSlot::iter().map(|i| i.to_string()).collect() + } +} + +impl fmt::Display for RecordSlot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str().unwrap()) + } } // Input Playback Slot @@ -1371,6 +1450,16 @@ impl ToggleTrait for HitstunPlayback { fn to_toggle_vals() -> Vec<u32> { HitstunPlayback::iter().map(|i| i as u32).collect() } + + fn to_toggle_strings() -> Vec<String> { + HitstunPlayback::iter().map(|i| i.to_string()).collect() + } +} + +impl fmt::Display for HitstunPlayback { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str().unwrap()) + } } // Input Recording Trigger Type @@ -1462,6 +1551,16 @@ impl ToggleTrait for RecordingFrames { fn to_toggle_vals() -> Vec<u32> { RecordingFrames::iter().map(|i| i as u32).collect() } + + fn to_toggle_strings() -> Vec<String> { + RecordingFrames::iter().map(|i| i.to_string()).collect() + } +} + +impl fmt::Display for RecordingFrames { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str().unwrap()) + } } bitflags! { @@ -1508,6 +1607,13 @@ impl ButtonConfig { _ => return None, }) } + pub fn combination_string(&self) -> String { + self.to_vec() + .iter() + .map(|button| button.as_str().unwrap()) + .intersperse(" + ") + .collect::<String>() + } } extra_bitflag_impls! {ButtonConfig} diff --git a/training_mod_tui/src/lib.rs b/training_mod_tui/src/lib.rs index 3cd4045..0ca02a1 100644 --- a/training_mod_tui/src/lib.rs +++ b/training_mod_tui/src/lib.rs @@ -35,27 +35,29 @@ pub enum AppPage { /// We should hold a list of SubMenus. /// The currently selected SubMenu should also have an associated list with necessary information. /// We can convert the option types (Toggle, OnOff, Slider) to lists -pub struct App<'a> { - pub tabs: StatefulList<&'a str>, - pub menu_items: HashMap<&'a str, MultiStatefulList<SubMenu<'a>>>, - pub selected_sub_menu_toggles: MultiStatefulList<Toggle<'a>>, +pub struct App { + pub tabs: StatefulList<String>, + pub menu_items: HashMap<String, MultiStatefulList<SubMenu>>, + pub selected_sub_menu_toggles: MultiStatefulList<Toggle>, pub selected_sub_menu_slider: DoubleEndedGauge, pub page: AppPage, - pub default_menu: (UiMenu<'a>, String), + pub default_menu: (UiMenu, String), } -impl<'a> App<'a> { - pub fn new(menu: UiMenu<'a>, default_menu: (UiMenu<'a>, String)) -> App<'a> { +impl<'a> App { + pub fn new(menu: UiMenu, default_menu: (UiMenu, String)) -> App { let mut menu_items_stateful = HashMap::new(); menu.tabs.iter().for_each(|tab| { menu_items_stateful.insert( - tab.tab_title, + tab.tab_title.clone(), MultiStatefulList::with_items(tab.tab_submenus.clone(), NUM_LISTS), ); }); let mut app = App { - tabs: StatefulList::with_items(menu.tabs.iter().map(|tab| tab.tab_title).collect()), + tabs: StatefulList::with_items( + menu.tabs.iter().map(|tab| tab.tab_title.clone()).collect(), + ), menu_items: menu_items_stateful, selected_sub_menu_toggles: MultiStatefulList::with_items(vec![], 0), selected_sub_menu_slider: DoubleEndedGauge::new(), @@ -82,7 +84,7 @@ impl<'a> App<'a> { let toggles = selected_sub_menu.toggles.clone(); let slider = selected_sub_menu.slider.clone(); - match SubMenuType::from_str(self.sub_menu_selected()._type) { + match SubMenuType::from_string(&self.sub_menu_selected()._type) { SubMenuType::TOGGLE => { self.selected_sub_menu_toggles = MultiStatefulList::with_items( toggles, @@ -117,13 +119,13 @@ impl<'a> App<'a> { /// Returns the currently selected SubMenu struct /// /// { - /// submenu_title: &'a str, - /// submenu_id: &'a str, - /// help_text: &'a str, + /// submenu_title: String, + /// submenu_id: String, + /// help_text: String, /// is_single_option: bool, /// toggles: Vec<Toggle<'a>>, /// slider: Option<Slider>, - /// _type: &'a str, + /// _type: String, /// } fn sub_menu_selected(&self) -> &SubMenu { let (list_section, list_idx) = self @@ -141,7 +143,7 @@ impl<'a> App<'a> { /// Toggles: calls next() /// Slider: Swaps between MinHover and MaxHover pub fn sub_menu_next(&mut self) { - match SubMenuType::from_str(self.sub_menu_selected()._type) { + match SubMenuType::from_string(&self.sub_menu_selected()._type) { SubMenuType::TOGGLE => self.selected_sub_menu_toggles.next(), SubMenuType::SLIDER => match self.selected_sub_menu_slider.state { GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover, @@ -157,7 +159,7 @@ impl<'a> App<'a> { /// * Swaps between MinHover and MaxHover /// * Increments the selected_min/max if possible pub fn sub_menu_next_list(&mut self) { - match SubMenuType::from_str(self.sub_menu_selected()._type) { + match SubMenuType::from_string(&self.sub_menu_selected()._type) { SubMenuType::TOGGLE => self.selected_sub_menu_toggles.next_list(), SubMenuType::SLIDER => match self.selected_sub_menu_slider.state { GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover, @@ -185,7 +187,7 @@ impl<'a> App<'a> { /// Toggles: calls previous() /// Slider: Swaps between MinHover and MaxHover pub fn sub_menu_previous(&mut self) { - match SubMenuType::from_str(self.sub_menu_selected()._type) { + match SubMenuType::from_string(&self.sub_menu_selected()._type) { SubMenuType::TOGGLE => self.selected_sub_menu_toggles.previous(), SubMenuType::SLIDER => match self.selected_sub_menu_slider.state { GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover, @@ -201,7 +203,7 @@ impl<'a> App<'a> { /// * Swaps between MinHover and MaxHover /// * Decrements the selected_min/max if possible pub fn sub_menu_previous_list(&mut self) { - match SubMenuType::from_str(self.sub_menu_selected()._type) { + match SubMenuType::from_string(&self.sub_menu_selected()._type) { SubMenuType::TOGGLE => self.selected_sub_menu_toggles.previous_list(), SubMenuType::SLIDER => match self.selected_sub_menu_slider.state { GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover, @@ -232,11 +234,13 @@ impl<'a> App<'a> { /// 2: Vec(toggle checked, title) for toggles, Vec(nothing) for slider /// 3: ListState for toggles, ListState::new() for slider /// TODO: Refactor return type into a nice struct - pub fn sub_menu_strs_and_states(&self) -> (&str, &str, Vec<(Vec<(bool, &str)>, ListState)>) { + pub fn sub_menu_strs_and_states( + &self, + ) -> (String, String, Vec<(Vec<(bool, String)>, ListState)>) { ( - self.sub_menu_selected().submenu_title, - self.sub_menu_selected().help_text, - match SubMenuType::from_str(self.sub_menu_selected()._type) { + self.sub_menu_selected().submenu_title.clone(), + self.sub_menu_selected().help_text.clone(), + match SubMenuType::from_string(&self.sub_menu_selected()._type) { SubMenuType::TOGGLE => self .selected_sub_menu_toggles .lists @@ -246,7 +250,7 @@ impl<'a> App<'a> { toggle_list .items .iter() - .map(|toggle| (toggle.checked, toggle.toggle_title)) + .map(|toggle| (toggle.checked, toggle.toggle_title.clone())) .collect(), toggle_list.state.clone(), ) @@ -264,16 +268,16 @@ impl<'a> App<'a> { /// 1: Help text /// 2: Reference to self.selected_sub_menu_slider /// TODO: Refactor return type into a nice struct - pub fn sub_menu_strs_for_slider(&self) -> (&str, &str, &DoubleEndedGauge) { - let slider = match SubMenuType::from_str(self.sub_menu_selected()._type) { + pub fn sub_menu_strs_for_slider(&self) -> (String, String, &DoubleEndedGauge) { + let slider = match SubMenuType::from_string(&self.sub_menu_selected()._type) { SubMenuType::SLIDER => &self.selected_sub_menu_slider, _ => { panic!("Slider not selected!"); } }; ( - self.sub_menu_selected().submenu_title, - self.sub_menu_selected().help_text, + self.sub_menu_selected().submenu_title.clone(), + self.sub_menu_selected().help_text.clone(), slider, ) } @@ -298,7 +302,7 @@ impl<'a> App<'a> { .get_mut(list_idx) .unwrap(); if self.page == AppPage::SUBMENU { - match SubMenuType::from_str(selected_sub_menu._type) { + match SubMenuType::from_string(&selected_sub_menu._type) { // Need to change the slider state to MinHover so the slider shows up initially SubMenuType::SLIDER => { self.page = AppPage::SLIDER; @@ -307,7 +311,7 @@ impl<'a> App<'a> { SubMenuType::TOGGLE => self.page = AppPage::TOGGLE, } } else { - match SubMenuType::from_str(selected_sub_menu._type) { + match SubMenuType::from_string(&selected_sub_menu._type) { SubMenuType::TOGGLE => { let is_single_option = selected_sub_menu.is_single_option; let state = self.selected_sub_menu_toggles.state; @@ -404,7 +408,7 @@ impl<'a> App<'a> { .items .get_mut(list_idx) .unwrap(); - match SubMenuType::from_str(selected_sub_menu._type) { + match SubMenuType::from_string(&selected_sub_menu._type) { SubMenuType::SLIDER => match self.selected_sub_menu_slider.state { GaugeState::MinSelected => { self.selected_sub_menu_slider.state = GaugeState::MinHover; @@ -455,10 +459,11 @@ impl<'a> App<'a> { let json = self.to_json(); let mut json_value = serde_json::from_str::<serde_json::Value>(&json).unwrap(); let selected_sub_menu = self.sub_menu_selected(); - let id = selected_sub_menu.submenu_id; + let id = selected_sub_menu.submenu_id.clone(); let default_json_value = serde_json::from_str::<serde_json::Value>(&self.default_menu.1).unwrap(); - *json_value.get_mut(id).unwrap() = default_json_value.get(id).unwrap().clone(); + *json_value.get_mut(id.as_str()).unwrap() = + default_json_value.get(id.as_str()).unwrap().clone(); let new_menu = serde_json::from_value::<TrainingModpackMenu>(json_value).unwrap(); *self = App::new(unsafe { ui_menu(new_menu) }, self.default_menu.clone()); } @@ -587,7 +592,7 @@ impl<'a> App<'a> { .unwrap() } - pub fn submenu_ids(&self) -> Vec<&str> { + pub fn submenu_ids(&self) -> Vec<String> { return self .menu_items .values() @@ -599,10 +604,10 @@ impl<'a> App<'a> { sub_stateful_list .items .iter() - .map(|submenu| submenu.submenu_id) + .map(|submenu| submenu.submenu_id.clone()) }) }) - .collect::<Vec<&str>>(); + .collect::<Vec<String>>(); } } @@ -627,9 +632,9 @@ fn render_submenu_page<B: Backend>( .iter() .map(|i| { let lines = vec![Spans::from(if stateful_list.state.selected().is_some() { - i.submenu_title.to_owned() + i.submenu_title.clone() } else { - " ".to_owned() + i.submenu_title + format!(" {}", i.submenu_title.clone()) })]; ListItem::new(lines).style(Style::default().fg(Color::White)) }) @@ -650,14 +655,18 @@ fn render_submenu_page<B: Backend>( let mut state = stateful_list.state.clone(); if state.selected().is_some() { - item_help = Some(stateful_list.items[state.selected().unwrap()].help_text); + item_help = Some( + stateful_list.items[state.selected().unwrap()] + .help_text + .clone(), + ); } f.render_stateful_widget(list, list_chunks[list_section], &mut state); } let help_paragraph = Paragraph::new( - item_help.unwrap_or("").replace('\"', "") + item_help.unwrap_or("".to_string()).replace('\"', "") + "\nZL/ZR: Next tab | X: Save Defaults | R: Reset All Menus", ) .style(Style::default().fg(Color::Cyan)); @@ -677,14 +686,20 @@ pub fn render_toggle_page<B: Backend>( let values_items: Vec<ListItem> = sub_menu_str .iter() .map(|s| { - ListItem::new(vec![Spans::from( - (if s.0 { "X " } else { " " }).to_owned() + s.1, - )]) + ListItem::new(vec![Spans::from(if s.0 { + format!("X {}", s.1) + } else { + format!(" {}", s.1) + })]) }) .collect(); let values_list = List::new(values_items) - .block(Block::default().title(if list_section == 0 { title } else { "" })) + .block(Block::default().title(if list_section == 0 { + title.clone() + } else { + "".to_string() + })) .start_corner(Corner::TopLeft) .highlight_style( Style::default() @@ -795,10 +810,10 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) { .enumerate() .map(|(idx, tab)| { if idx == tab_selected { - span_selected = Spans::from("> ".to_owned() + tab); - Spans::from("> ".to_owned() + tab) + span_selected = Spans::from(format!("> {}", tab)); + Spans::from(format!("> {}", tab)) } else { - Spans::from(" ".to_owned() + tab) + Spans::from(format!(" {}", tab)) } }) .collect(); diff --git a/training_mod_tui/src/main.rs b/training_mod_tui/src/main.rs index df0220c..a10e722 100644 --- a/training_mod_tui/src/main.rs +++ b/training_mod_tui/src/main.rs @@ -17,17 +17,17 @@ use tui::Terminal; use training_mod_consts::*; -fn test_backend_setup<'a>( - ui_menu: UiMenu<'a>, - menu_defaults: (UiMenu<'a>, String), +fn test_backend_setup( + ui_menu: UiMenu, + menu_defaults: (UiMenu, String), ) -> Result< ( Terminal<training_mod_tui::TestBackend>, - training_mod_tui::App<'a>, + training_mod_tui::App, ), Box<dyn Error>, > { - let app = training_mod_tui::App::<'a>::new(ui_menu, menu_defaults); + let app = training_mod_tui::App::new(ui_menu, menu_defaults); let backend = tui::backend::TestBackend::new(120, 15); let terminal = Terminal::new(backend)?; let mut state = tui::widgets::ListState::default(); From 6f1e4af332a375e37968fcb452436125bc388cef Mon Sep 17 00:00:00 2001 From: jugeeya <jugeeya@live.com> Date: Tue, 15 Aug 2023 23:57:38 -0700 Subject: [PATCH 2/4] Don't set CPU controls outside of training --- src/training/input_record.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/training/input_record.rs b/src/training/input_record.rs index b59b9e2..7625be6 100644 --- a/src/training/input_record.rs +++ b/src/training/input_record.rs @@ -423,6 +423,10 @@ pub unsafe fn handle_final_input_mapping(player_idx: i32, out: *mut MappedInputs #[skyline::hook(offset = 0x2da180)] // After cpu controls are assigned from ai calls unsafe fn set_cpu_controls(p_data: *mut *mut u8) { call_original!(p_data); + if !is_training_mode() { + return; + } + let controller_data = *p_data.add(1) as *mut ControlModuleInternal; let _controller_no = (*controller_data).controller_index; From e78ab92d4bf3eca93fb25ef403956c2513dc829f Mon Sep 17 00:00:00 2001 From: Austin Traver <austintraver@gmail.com> Date: Wed, 16 Aug 2023 06:15:11 -0700 Subject: [PATCH 3/4] Fix soft-freeze on loading Quickplay lobby (#593) * Fix soft-freeze on loading Quickplay lobby * Formatting --------- Co-authored-by: jugeeya <jugeeya@live.com> --- src/training/input_record.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/training/input_record.rs b/src/training/input_record.rs index 7625be6..bd79d54 100644 --- a/src/training/input_record.rs +++ b/src/training/input_record.rs @@ -1,15 +1,20 @@ -use crate::common::button_config; -use crate::common::consts::{FighterId, HitstunPlayback, OnOff, RecordTrigger}; -use crate::common::input::*; -use crate::common::{get_module_accessor, is_in_hitstun, is_in_shieldstun, MENU}; -use crate::training::mash; -use crate::training::ui::notifications::{clear_notifications, color_notification}; +use std::cmp::Ordering; + use lazy_static::lazy_static; use parking_lot::Mutex; use skyline::nn::ui2d::ResColor; use smash::app::{lua_bind::*, utility, BattleObjectModuleAccessor}; use smash::lib::lua_const::*; -use std::cmp::Ordering; + +use InputRecordState::*; +use PossessionState::*; + +use crate::common::consts::{FighterId, HitstunPlayback, OnOff, RecordTrigger}; +use crate::common::input::*; +use crate::common::{button_config, is_training_mode}; +use crate::common::{get_module_accessor, is_in_hitstun, is_in_shieldstun, MENU}; +use crate::training::mash; +use crate::training::ui::notifications::{clear_notifications, color_notification}; #[derive(PartialEq, Debug)] pub enum InputRecordState { @@ -43,9 +48,6 @@ pub enum StartingStatus { Other, } -use InputRecordState::*; -use PossessionState::*; - const STICK_NEUTRAL: f32 = 0.2; const STICK_CLAMP_MULTIPLIER: f32 = 1.0 / 120.0; // 120.0 = CLAMP_MAX const FINAL_RECORD_MAX: usize = 600; // Maximum length for input recording sequences (capacity) From 0f808320c334b0e46d627ba009ea74970abb675f Mon Sep 17 00:00:00 2001 From: jugeeya <jugeeya@live.com> Date: Wed, 16 Aug 2023 06:29:14 -0700 Subject: [PATCH 4/4] Fix formatting issue (#594) --- src/training/input_record.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/training/input_record.rs b/src/training/input_record.rs index bd79d54..07e4fd3 100644 --- a/src/training/input_record.rs +++ b/src/training/input_record.rs @@ -428,7 +428,7 @@ unsafe fn set_cpu_controls(p_data: *mut *mut u8) { if !is_training_mode() { return; } - + let controller_data = *p_data.add(1) as *mut ControlModuleInternal; let _controller_no = (*controller_data).controller_index;