1
0
Fork 0
mirror of https://github.com/jugeeya/UltimateTrainingModpack.git synced 2024-11-24 10:54:16 +00:00

Refactor string representation for buttons and menus (#592)

* Refactor string representation for buttons and menus

* Pass test cases

* Update help text
This commit is contained in:
Austin Traver 2023-08-15 19:37:35 -07:00 committed by GitHub
parent f862abacaf
commit 10ebff5a6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 462 additions and 357 deletions

View file

@ -5,7 +5,6 @@ use lazy_static::lazy_static;
use parking_lot::Mutex; use parking_lot::Mutex;
use skyline::nn::hid::GetNpadStyleSet; use skyline::nn::hid::GetNpadStyleSet;
use training_mod_consts::MenuJsonStruct; use training_mod_consts::MenuJsonStruct;
use training_mod_tui::AppPage; use training_mod_tui::AppPage;
use crate::common::button_config::button_mapping; use crate::common::button_config::button_mapping;
@ -95,7 +94,7 @@ enum DirectionButton {
} }
lazy_static! { 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 { training_mod_tui::App::new(unsafe { ui_menu(MENU) }, unsafe {
( (
ui_menu(DEFAULTS_MENU), ui_menu(DEFAULTS_MENU),

View file

@ -106,54 +106,25 @@ pub fn main() {
unsafe { unsafe {
notification("Training Modpack".to_string(), "Welcome!".to_string(), 60); notification("Training Modpack".to_string(), "Welcome!".to_string(), 60);
notification( notification("Open Menu".to_string(), MENU.menu_open.to_string(), 120);
"Open Menu".to_string(),
MENU.menu_open
.to_vec()
.iter()
.map(|button| button.as_str().unwrap())
.intersperse(" + ")
.collect(),
120,
);
notification( notification(
"Save State".to_string(), "Save State".to_string(),
MENU.save_state_save MENU.save_state_save.to_string(),
.to_vec()
.iter()
.map(|button| button.as_str().unwrap())
.intersperse(" + ")
.collect(),
120, 120,
); );
notification( notification(
"Load State".to_string(), "Load State".to_string(),
MENU.save_state_load MENU.save_state_load.to_string(),
.to_vec()
.iter()
.map(|button| button.as_str().unwrap())
.intersperse(" + ")
.collect(),
120, 120,
); );
notification( notification(
"Input Record".to_string(), "Input Record".to_string(),
MENU.input_record MENU.input_record.to_string(),
.to_vec()
.iter()
.map(|button| button.as_str().unwrap())
.intersperse(" + ")
.collect(),
120, 120,
); );
notification( notification(
"Input Playback".to_string(), "Input Playback".to_string(),
MENU.input_playback MENU.input_playback.to_string(),
.to_vec()
.iter()
.map(|button| button.as_str().unwrap())
.intersperse(" + ")
.collect(),
120, 120,
); );
} }

View file

@ -105,7 +105,7 @@ unsafe fn render_submenu_page(app: &App, root_pane: &Pane) {
let submenu = &list.items[list_idx]; let submenu = &list.items[list_idx];
let is_selected = list.state.selected().filter(|s| *s == list_idx).is_some(); 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 // In the actual 'layout.arc' file, every icon image is stacked
// into a single container pane, with each image directly on top of another. // 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") .find_pane_by_name_recursive("FooterTxt")
.unwrap() .unwrap()
.as_textbox() .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_white_res_color(BG_LEFT_ON_WHITE_COLOR);
title_bg_material.set_black_res_color(BG_LEFT_ON_BLACK_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") .find_pane_by_name_recursive("Header")
.unwrap() .unwrap()
.as_textbox(); .as_textbox();
header.set_text_string(title); header.set_text_string(title.as_str());
let min_button = slider_pane let min_button = slider_pane
.find_pane_by_name_recursive("MinButton") .find_pane_by_name_recursive("MinButton")
.unwrap() .unwrap()
@ -411,7 +411,7 @@ pub unsafe fn draw(root_pane: &Pane) {
} else { } else {
tab_selected + 1 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 is_gcc = (*common::menu::P1_CONTROLLER_STYLE.data_ptr()) == ControllerStyle::GCController;
let button_mapping = if is_gcc { 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_default_material_colors();
help_pane.set_color(255, 255, 0, 255); 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"), (save_defaults_key, "SaveDefaults", "Save Defaults"),

View file

@ -1,3 +1,4 @@
#![feature(iter_intersperse)]
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
@ -111,8 +112,8 @@ pub enum SubMenuType {
} }
impl SubMenuType { impl SubMenuType {
pub fn from_str(s: &str) -> SubMenuType { pub fn from_string(s: &String) -> SubMenuType {
match s { match s.as_str() {
"toggle" => SubMenuType::TOGGLE, "toggle" => SubMenuType::TOGGLE,
"slider" => SubMenuType::SLIDER, "slider" => SubMenuType::SLIDER,
_ => panic!("Unexpected SubMenuType!"), _ => panic!("Unexpected SubMenuType!"),
@ -203,25 +204,25 @@ pub struct Slider {
} }
#[derive(Clone, Serialize)] #[derive(Clone, Serialize)]
pub struct Toggle<'a> { pub struct Toggle {
pub toggle_value: u32, pub toggle_value: u32,
pub toggle_title: &'a str, pub toggle_title: String,
pub checked: bool, pub checked: bool,
} }
#[derive(Clone, Serialize)] #[derive(Clone, Serialize)]
pub struct SubMenu<'a> { pub struct SubMenu {
pub submenu_title: &'a str, pub submenu_title: String,
pub submenu_id: &'a str, pub submenu_id: String,
pub help_text: &'a str, pub help_text: String,
pub is_single_option: bool, pub is_single_option: bool,
pub toggles: Vec<Toggle<'a>>, pub toggles: Vec<Toggle>,
pub slider: Option<Slider>, pub slider: Option<Slider>,
pub _type: &'a str, pub _type: String,
} }
impl<'a> SubMenu<'a> { impl SubMenu {
pub fn add_toggle(&mut self, toggle_value: u32, toggle_title: &'a str, checked: bool) { pub fn add_toggle(&mut self, toggle_value: u32, toggle_title: String, checked: bool) {
self.toggles.push(Toggle { self.toggles.push(Toggle {
toggle_value, toggle_value,
toggle_title, toggle_title,
@ -229,12 +230,12 @@ impl<'a> SubMenu<'a> {
}); });
} }
pub fn new_with_toggles<T: ToggleTrait>( pub fn new_with_toggles<T: ToggleTrait>(
submenu_title: &'a str, submenu_title: String,
submenu_id: &'a str, submenu_id: String,
help_text: &'a str, help_text: String,
is_single_option: bool, is_single_option: bool,
initial_value: &u32, initial_value: &u32,
) -> SubMenu<'a> { ) -> SubMenu {
let mut instance = SubMenu { let mut instance = SubMenu {
submenu_title: submenu_title, submenu_title: submenu_title,
submenu_id: submenu_id, submenu_id: submenu_id,
@ -242,15 +243,15 @@ impl<'a> SubMenu<'a> {
is_single_option: is_single_option, is_single_option: is_single_option,
toggles: Vec::new(), toggles: Vec::new(),
slider: None, slider: None,
_type: "toggle", _type: "toggle".to_string(),
}; };
let values = T::to_toggle_vals(); let values = T::to_toggle_vals();
let titles = T::to_toggle_strs(); let titles = T::to_toggle_strings();
for i in 0..values.len() { for i in 0..values.len() {
let checked: bool = let checked: bool =
(values[i] & initial_value) > 0 || (!values[i] == 0 && initial_value == &0); (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 // 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) { if is_single_option && instance.toggles.iter().all(|t| !t.checked) {
@ -259,12 +260,12 @@ impl<'a> SubMenu<'a> {
instance instance
} }
pub fn new_with_slider<S: SliderTrait>( pub fn new_with_slider<S: SliderTrait>(
submenu_title: &'a str, submenu_title: String,
submenu_id: &'a str, submenu_id: String,
help_text: &'a str, help_text: String,
initial_lower_value: &u32, initial_lower_value: &u32,
initial_upper_value: &u32, initial_upper_value: &u32,
) -> SubMenu<'a> { ) -> SubMenu {
let min_max = S::get_limits(); let min_max = S::get_limits();
SubMenu { SubMenu {
submenu_title: submenu_title, submenu_title: submenu_title,
@ -278,31 +279,31 @@ impl<'a> SubMenu<'a> {
abs_min: min_max.0, abs_min: min_max.0,
abs_max: min_max.1, abs_max: min_max.1,
}), }),
_type: "slider", _type: "slider".to_string(),
} }
} }
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
pub struct Tab<'a> { pub struct Tab {
pub tab_id: &'a str, pub tab_id: String,
pub tab_title: &'a str, pub tab_title: String,
pub tab_submenus: Vec<SubMenu<'a>>, pub tab_submenus: Vec<SubMenu>,
} }
impl<'a> Tab<'a> { impl Tab {
pub fn add_submenu_with_toggles<T: ToggleTrait>( pub fn add_submenu_with_toggles<T: ToggleTrait>(
&mut self, &mut self,
submenu_title: &'a str, submenu_title: String,
submenu_id: &'a str, submenu_id: String,
help_text: &'a str, help_text: String,
is_single_option: bool, is_single_option: bool,
initial_value: &u32, initial_value: &u32,
) { ) {
self.tab_submenus.push(SubMenu::new_with_toggles::<T>( self.tab_submenus.push(SubMenu::new_with_toggles::<T>(
submenu_title, submenu_title.to_string(),
submenu_id, submenu_id.to_string(),
help_text, help_text.to_string(),
is_single_option, is_single_option,
initial_value, initial_value,
)); ));
@ -310,16 +311,16 @@ impl<'a> Tab<'a> {
pub fn add_submenu_with_slider<S: SliderTrait>( pub fn add_submenu_with_slider<S: SliderTrait>(
&mut self, &mut self,
submenu_title: &'a str, submenu_title: String,
submenu_id: &'a str, submenu_id: String,
help_text: &'a str, help_text: String,
initial_lower_value: &u32, initial_lower_value: &u32,
initial_upper_value: &u32, initial_upper_value: &u32,
) { ) {
self.tab_submenus.push(SubMenu::new_with_slider::<S>( self.tab_submenus.push(SubMenu::new_with_slider::<S>(
submenu_title, submenu_title.to_string(),
submenu_id, submenu_id.to_string(),
help_text, help_text.to_string(),
initial_lower_value, initial_lower_value,
initial_upper_value, initial_upper_value,
)) ))
@ -327,542 +328,555 @@ impl<'a> Tab<'a> {
} }
#[derive(Serialize, Clone)] #[derive(Serialize, Clone)]
pub struct UiMenu<'a> { pub struct UiMenu {
pub tabs: Vec<Tab<'a>>, 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 overall_menu = UiMenu { tabs: Vec::new() };
let mut mash_tab = Tab { let mut mash_tab = Tab {
tab_id: "mash", tab_id: "mash".to_string(),
tab_title: "Mash Settings", tab_title: "Mash Settings".to_string(),
tab_submenus: Vec::new(), tab_submenus: Vec::new(),
}; };
mash_tab.add_submenu_with_toggles::<Action>( mash_tab.add_submenu_with_toggles::<Action>(
"Mash Toggles", "Mash Toggles".to_string(),
"mash_state", "mash_state".to_string(),
"Mash Toggles: Actions to be performed as soon as possible", "Mash Toggles: Actions to be performed as soon as possible".to_string(),
false, false,
&(menu.mash_state.bits()), &(menu.mash_state.bits()),
); );
mash_tab.add_submenu_with_toggles::<Action>( mash_tab.add_submenu_with_toggles::<Action>(
"Followup Toggles", "Followup Toggles".to_string(),
"follow_up", "follow_up".to_string(),
"Followup Toggles: Actions to be performed after a Mash option", "Followup Toggles: Actions to be performed after a Mash option".to_string(),
false, false,
&(menu.follow_up.bits()), &(menu.follow_up.bits()),
); );
mash_tab.add_submenu_with_toggles::<MashTrigger>( mash_tab.add_submenu_with_toggles::<MashTrigger>(
"Mash Triggers", "Mash Triggers".to_string(),
"mash_triggers", "mash_triggers".to_string(),
"Mash triggers: Configure what causes the CPU to perform a Mash option", "Mash triggers: Configure what causes the CPU to perform a Mash option".to_string(),
false, false,
&(menu.mash_triggers.bits()), &(menu.mash_triggers.bits()),
); );
mash_tab.add_submenu_with_toggles::<AttackAngle>( mash_tab.add_submenu_with_toggles::<AttackAngle>(
"Attack Angle", "Attack Angle".to_string(),
"attack_angle", "attack_angle".to_string(),
"Attack Angle: For attacks that can be angled, such as some forward tilts", "Attack Angle: For attacks that can be angled, such as some forward tilts".to_string(),
false, false,
&(menu.attack_angle.bits()), &(menu.attack_angle.bits()),
); );
mash_tab.add_submenu_with_toggles::<ThrowOption>( mash_tab.add_submenu_with_toggles::<ThrowOption>(
"Throw Options", "Throw Options".to_string(),
"throw_state", "throw_state".to_string(),
"Throw Options: Throw to be performed when a grab is landed", "Throw Options: Throw to be performed when a grab is landed".to_string(),
false, false,
&(menu.throw_state.bits()), &(menu.throw_state.bits()),
); );
mash_tab.add_submenu_with_toggles::<MedDelay>( mash_tab.add_submenu_with_toggles::<MedDelay>(
"Throw Delay", "Throw Delay".to_string(),
"throw_delay", "throw_delay".to_string(),
"Throw Delay: How many frames to delay the throw option", "Throw Delay: How many frames to delay the throw option".to_string(),
false, false,
&(menu.throw_delay.bits()), &(menu.throw_delay.bits()),
); );
mash_tab.add_submenu_with_toggles::<MedDelay>( mash_tab.add_submenu_with_toggles::<MedDelay>(
"Pummel Delay", "Pummel Delay".to_string(),
"pummel_delay", "pummel_delay".to_string(),
"Pummel Delay: How many frames after a grab to wait before starting to pummel", "Pummel Delay: How many frames after a grab to wait before starting to pummel".to_string(),
false, false,
&(menu.pummel_delay.bits()), &(menu.pummel_delay.bits()),
); );
mash_tab.add_submenu_with_toggles::<BoolFlag>( mash_tab.add_submenu_with_toggles::<BoolFlag>(
"Falling Aerials", "Falling Aerials".to_string(),
"falling_aerials", "falling_aerials".to_string(),
"Falling Aerials: Should aerials be performed when rising or when falling", "Falling Aerials: Should aerials be performed when rising or when falling".to_string(),
false, false,
&(menu.falling_aerials.bits()), &(menu.falling_aerials.bits()),
); );
mash_tab.add_submenu_with_toggles::<BoolFlag>( mash_tab.add_submenu_with_toggles::<BoolFlag>(
"Full Hop", "Full Hop".to_string(),
"full_hop", "full_hop".to_string(),
"Full Hop: Should the CPU perform a full hop or a short hop", "Full Hop: Should the CPU perform a full hop or a short hop".to_string(),
false, false,
&(menu.full_hop.bits()), &(menu.full_hop.bits()),
); );
mash_tab.add_submenu_with_toggles::<Delay>( mash_tab.add_submenu_with_toggles::<Delay>(
"Aerial Delay", "Aerial Delay".to_string(),
"aerial_delay", "aerial_delay".to_string(),
"Aerial Delay: How long to delay a Mash aerial attack", "Aerial Delay: How long to delay a Mash aerial attack".to_string(),
false, false,
&(menu.aerial_delay.bits()), &(menu.aerial_delay.bits()),
); );
mash_tab.add_submenu_with_toggles::<BoolFlag>( mash_tab.add_submenu_with_toggles::<BoolFlag>(
"Fast Fall", "Fast Fall".to_string(),
"fast_fall", "fast_fall".to_string(),
"Fast Fall: Should the CPU fastfall during a jump", "Fast Fall: Should the CPU fastfall during a jump".to_string(),
false, false,
&(menu.fast_fall.bits()), &(menu.fast_fall.bits()),
); );
mash_tab.add_submenu_with_toggles::<Delay>( mash_tab.add_submenu_with_toggles::<Delay>(
"Fast Fall Delay", "Fast Fall Delay".to_string(),
"fast_fall_delay", "fast_fall_delay".to_string(),
"Fast Fall Delay: How many frames the CPU should delay their fastfall", "Fast Fall Delay: How many frames the CPU should delay their fastfall".to_string(),
false, false,
&(menu.fast_fall_delay.bits()), &(menu.fast_fall_delay.bits()),
); );
mash_tab.add_submenu_with_toggles::<Delay>( mash_tab.add_submenu_with_toggles::<Delay>(
"OoS Offset", "OoS Offset".to_string(),
"oos_offset", "oos_offset".to_string(),
"OoS Offset: How many times the CPU shield can be hit before performing a Mash option", "OoS Offset: How many times the CPU shield can be hit before performing a Mash option"
.to_string(),
false, false,
&(menu.oos_offset.bits()), &(menu.oos_offset.bits()),
); );
mash_tab.add_submenu_with_toggles::<Delay>( mash_tab.add_submenu_with_toggles::<Delay>(
"Reaction Time", "Reaction Time".to_string(),
"reaction_time", "reaction_time".to_string(),
"Reaction Time: How many frames to delay before performing a mash option", "Reaction Time: How many frames to delay before performing a mash option".to_string(),
false, false,
&(menu.reaction_time.bits()), &(menu.reaction_time.bits()),
); );
overall_menu.tabs.push(mash_tab); overall_menu.tabs.push(mash_tab);
let mut override_tab = Tab { let mut override_tab = Tab {
tab_id: "override", tab_id: "override".to_string(),
tab_title: "Override Settings", tab_title: "Override Settings".to_string(),
tab_submenus: Vec::new(), tab_submenus: Vec::new(),
}; };
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Ledge Neutral Getup", "Ledge Neutral Getup".to_string(),
"ledge_neutral_override", "ledge_neutral_override".to_string(),
"Neutral Getup Override: Mash Actions to be performed after a Neutral Getup from ledge", "Neutral Getup Override: Mash Actions to be performed after a Neutral Getup from ledge"
.to_string(),
false, false,
&(menu.ledge_neutral_override.bits()), &(menu.ledge_neutral_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Ledge Roll", "Ledge Roll".to_string(),
"ledge_roll_override", "ledge_roll_override".to_string(),
"Ledge Roll Override: Mash Actions to be performed after a Roll Getup from ledge", "Ledge Roll Override: Mash Actions to be performed after a Roll Getup from ledge"
.to_string(),
false, false,
&(menu.ledge_roll_override.bits()), &(menu.ledge_roll_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Ledge Jump", "Ledge Jump".to_string(),
"ledge_jump_override", "ledge_jump_override".to_string(),
"Ledge Jump Override: Mash Actions to be performed after a Jump Getup from ledge", "Ledge Jump Override: Mash Actions to be performed after a Jump Getup from ledge"
.to_string(),
false, false,
&(menu.ledge_jump_override.bits()), &(menu.ledge_jump_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Ledge Attack", "Ledge Attack".to_string(),
"ledge_attack_override", "ledge_attack_override".to_string(),
"Ledge Attack Override: Mash Actions to be performed after a Getup Attack from ledge", "Ledge Attack Override: Mash Actions to be performed after a Getup Attack from ledge"
.to_string(),
false, false,
&(menu.ledge_attack_override.bits()), &(menu.ledge_attack_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Tech Action", "Tech Action".to_string(),
"tech_action_override", "tech_action_override".to_string(),
"Tech Action Override: Mash Actions to be performed after any tech action", "Tech Action Override: Mash Actions to be performed after any tech action".to_string(),
false, false,
&(menu.tech_action_override.bits()), &(menu.tech_action_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Clatter", "Clatter".to_string(),
"clatter_override", "clatter_override".to_string(),
"Clatter Override: Mash Actions to be performed after leaving a clatter situation (grab, bury, etc)", "Clatter Override: Mash Actions to be performed after leaving a clatter situation (grab.to_string(), bury, etc)".to_string(),
false, false,
&(menu.clatter_override.bits()), &(menu.clatter_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Tumble", "Tumble".to_string(),
"tumble_override", "tumble_override".to_string(),
"Tumble Override: Mash Actions to be performed after exiting a tumble state", "Tumble Override: Mash Actions to be performed after exiting a tumble state".to_string(),
false, false,
&(menu.tumble_override.bits()), &(menu.tumble_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Hitstun", "Hitstun".to_string(),
"hitstun_override", "hitstun_override".to_string(),
"Hitstun Override: Mash Actions to be performed after exiting a hitstun state", "Hitstun Override: Mash Actions to be performed after exiting a hitstun state".to_string(),
false, false,
&(menu.hitstun_override.bits()), &(menu.hitstun_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Parry", "Parry".to_string(),
"parry_override", "parry_override".to_string(),
"Parry Override: Mash Actions to be performed after a parry", "Parry Override: Mash Actions to be performed after a parry".to_string(),
false, false,
&(menu.parry_override.bits()), &(menu.parry_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Shieldstun", "Shieldstun".to_string(),
"shieldstun_override", "shieldstun_override".to_string(),
"Shieldstun Override: Mash Actions to be performed after exiting a shieldstun state", "Shieldstun Override: Mash Actions to be performed after exiting a shieldstun state"
.to_string(),
false, false,
&(menu.shieldstun_override.bits()), &(menu.shieldstun_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Footstool", "Footstool".to_string(),
"footstool_override", "footstool_override".to_string(),
"Footstool Override: Mash Actions to be performed after exiting a footstool state", "Footstool Override: Mash Actions to be performed after exiting a footstool state"
.to_string(),
false, false,
&(menu.footstool_override.bits()), &(menu.footstool_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Landing", "Landing".to_string(),
"landing_override", "landing_override".to_string(),
"Landing Override: Mash Actions to be performed after landing on the ground", "Landing Override: Mash Actions to be performed after landing on the ground".to_string(),
false, false,
&(menu.landing_override.bits()), &(menu.landing_override.bits()),
); );
override_tab.add_submenu_with_toggles::<Action>( override_tab.add_submenu_with_toggles::<Action>(
"Ledge Trump", "Ledge Trump".to_string(),
"trump_override", "trump_override".to_string(),
"Ledge Trump Override: Mash Actions to be performed after leaving a ledgetrump state", "Ledge Trump Override: Mash Actions to be performed after leaving a ledgetrump state"
.to_string(),
false, false,
&(menu.trump_override.bits()), &(menu.trump_override.bits()),
); );
overall_menu.tabs.push(override_tab); overall_menu.tabs.push(override_tab);
let mut defensive_tab = Tab { let mut defensive_tab = Tab {
tab_id: "defensive", tab_id: "defensive".to_string(),
tab_title: "Defensive Settings", tab_title: "Defensive Settings".to_string(),
tab_submenus: Vec::new(), tab_submenus: Vec::new(),
}; };
defensive_tab.add_submenu_with_toggles::<Direction>( defensive_tab.add_submenu_with_toggles::<Direction>(
"Airdodge Direction", "Airdodge Direction".to_string(),
"air_dodge_dir", "air_dodge_dir".to_string(),
"Airdodge Direction: Direction to angle airdodges", "Airdodge Direction: Direction to angle airdodges".to_string(),
false, false,
&(menu.air_dodge_dir.bits()), &(menu.air_dodge_dir.bits()),
); );
defensive_tab.add_submenu_with_toggles::<Direction>( defensive_tab.add_submenu_with_toggles::<Direction>(
"DI Direction", "DI Direction".to_string(),
"di_state", "di_state".to_string(),
"DI Direction: Direction to angle the directional influence during hitlag", "DI Direction: Direction to angle the directional influence during hitlag".to_string(),
false, false,
&(menu.di_state.bits()), &(menu.di_state.bits()),
); );
defensive_tab.add_submenu_with_toggles::<Direction>( defensive_tab.add_submenu_with_toggles::<Direction>(
"SDI Direction", "SDI Direction".to_string(),
"sdi_state", "sdi_state".to_string(),
"SDI Direction: Direction to angle the smash directional influence during hitlag", "SDI Direction: Direction to angle the smash directional influence during hitlag"
.to_string(),
false, false,
&(menu.sdi_state.bits()), &(menu.sdi_state.bits()),
); );
defensive_tab.add_submenu_with_toggles::<SdiFrequency>( defensive_tab.add_submenu_with_toggles::<SdiFrequency>(
"SDI Strength", "SDI Strength".to_string(),
"sdi_strength", "sdi_strength".to_string(),
"SDI Strength: Relative strength of the smash directional influence inputs", "SDI Strength: Relative strength of the smash directional influence inputs".to_string(),
true, true,
&(menu.sdi_strength as u32), &(menu.sdi_strength as u32),
); );
defensive_tab.add_submenu_with_toggles::<ClatterFrequency>( defensive_tab.add_submenu_with_toggles::<ClatterFrequency>(
"Clatter Strength", "Clatter Strength".to_string(),
"clatter_strength", "clatter_strength".to_string(),
"Clatter Strength: Configure how rapidly the CPU will mash out of grabs, buries, etc.", "Clatter Strength: Configure how rapidly the CPU will mash out of grabs, buries, etc."
.to_string(),
true, true,
&(menu.clatter_strength as u32), &(menu.clatter_strength as u32),
); );
defensive_tab.add_submenu_with_toggles::<LedgeOption>( defensive_tab.add_submenu_with_toggles::<LedgeOption>(
"Ledge Options", "Ledge Options".to_string(),
"ledge_state", "ledge_state".to_string(),
"Ledge Options: Actions to be taken when on the ledge", "Ledge Options: Actions to be taken when on the ledge".to_string(),
false, false,
&(menu.ledge_state.bits()), &(menu.ledge_state.bits()),
); );
defensive_tab.add_submenu_with_toggles::<LongDelay>( defensive_tab.add_submenu_with_toggles::<LongDelay>(
"Ledge Delay", "Ledge Delay".to_string(),
"ledge_delay", "ledge_delay".to_string(),
"Ledge Delay: How many frames to delay the ledge option", "Ledge Delay: How many frames to delay the ledge option".to_string(),
false, false,
&(menu.ledge_delay.bits()), &(menu.ledge_delay.bits()),
); );
defensive_tab.add_submenu_with_toggles::<TechFlags>( defensive_tab.add_submenu_with_toggles::<TechFlags>(
"Tech Options", "Tech Options".to_string(),
"tech_state", "tech_state".to_string(),
"Tech Options: Actions to take when slammed into a hard surface", "Tech Options: Actions to take when slammed into a hard surface".to_string(),
false, false,
&(menu.tech_state.bits()), &(menu.tech_state.bits()),
); );
defensive_tab.add_submenu_with_toggles::<MissTechFlags>( defensive_tab.add_submenu_with_toggles::<MissTechFlags>(
"Mistech Options", "Mistech Options".to_string(),
"miss_tech_state", "miss_tech_state".to_string(),
"Mistech Options: Actions to take after missing a tech", "Mistech Options: Actions to take after missing a tech".to_string(),
false, false,
&(menu.miss_tech_state.bits()), &(menu.miss_tech_state.bits()),
); );
defensive_tab.add_submenu_with_toggles::<Shield>( defensive_tab.add_submenu_with_toggles::<Shield>(
"Shield Toggles", "Shield Toggles".to_string(),
"shield_state", "shield_state".to_string(),
"Shield Toggles: CPU Shield Behavior", "Shield Toggles: CPU Shield Behavior".to_string(),
true, true,
&(menu.shield_state as u32), &(menu.shield_state as u32),
); );
defensive_tab.add_submenu_with_toggles::<Direction>( defensive_tab.add_submenu_with_toggles::<Direction>(
"Shield Tilt", "Shield Tilt".to_string(),
"shield_tilt", "shield_tilt".to_string(),
"Shield Tilt: Direction to tilt the shield", "Shield Tilt: Direction to tilt the shield".to_string(),
false, // TODO: Should this be true? false, // TODO: Should this be true?
&(menu.shield_tilt.bits()), &(menu.shield_tilt.bits()),
); );
defensive_tab.add_submenu_with_toggles::<OnOff>( defensive_tab.add_submenu_with_toggles::<OnOff>(
"Crouch", "Crouch".to_string(),
"crouch", "crouch".to_string(),
"Crouch: Have the CPU crouch when on the ground", "Crouch: Have the CPU crouch when on the ground".to_string(),
true, true,
&(menu.crouch as u32), &(menu.crouch as u32),
); );
overall_menu.tabs.push(defensive_tab); overall_menu.tabs.push(defensive_tab);
let mut save_state_tab = Tab { let mut save_state_tab = Tab {
tab_id: "save_state", tab_id: "save_state".to_string(),
tab_title: "Save States", tab_title: "Save States".to_string(),
tab_submenus: Vec::new(), tab_submenus: Vec::new(),
}; };
save_state_tab.add_submenu_with_toggles::<SaveStateMirroring>( save_state_tab.add_submenu_with_toggles::<SaveStateMirroring>(
"Mirroring", "Mirroring".to_string(),
"save_state_mirroring", "save_state_mirroring".to_string(),
"Mirroring: Flips save states in the left-right direction across the stage center", "Mirroring: Flips save states in the left-right direction across the stage center"
.to_string(),
true, true,
&(menu.save_state_mirroring as u32), &(menu.save_state_mirroring as u32),
); );
save_state_tab.add_submenu_with_toggles::<OnOff>( save_state_tab.add_submenu_with_toggles::<OnOff>(
"Auto Save States", "Auto Save States".to_string(),
"save_state_autoload", "save_state_autoload".to_string(),
"Auto Save States: Load save state when any fighter dies", "Auto Save States: Load save state when any fighter dies".to_string(),
true, true,
&(menu.save_state_autoload as u32), &(menu.save_state_autoload as u32),
); );
save_state_tab.add_submenu_with_toggles::<SaveDamage>( save_state_tab.add_submenu_with_toggles::<SaveDamage>(
"Save Dmg (CPU)", "Save Dmg (CPU)".to_string(),
"save_damage_cpu", "save_damage_cpu".to_string(),
"Save Damage: Should save states retain CPU damage", "Save Damage: Should save states retain CPU damage".to_string(),
true, true,
&(menu.save_damage_cpu.bits()), &(menu.save_damage_cpu.bits()),
); );
save_state_tab.add_submenu_with_slider::<DamagePercent>( save_state_tab.add_submenu_with_slider::<DamagePercent>(
"Dmg Range (CPU)", "Dmg Range (CPU)".to_string(),
"save_damage_limits_cpu", "save_damage_limits_cpu".to_string(),
"Limits on random damage to apply to the CPU when loading a save state", "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.0 as u32),
&(menu.save_damage_limits_cpu.1 as u32), &(menu.save_damage_limits_cpu.1 as u32),
); );
save_state_tab.add_submenu_with_toggles::<SaveDamage>( save_state_tab.add_submenu_with_toggles::<SaveDamage>(
"Save Dmg (Player)", "Save Dmg (Player)".to_string(),
"save_damage_player", "save_damage_player".to_string(),
"Save Damage: Should save states retain player damage", "Save Damage: Should save states retain player damage".to_string(),
true, true,
&(menu.save_damage_player.bits() as u32), &(menu.save_damage_player.bits() as u32),
); );
save_state_tab.add_submenu_with_slider::<DamagePercent>( save_state_tab.add_submenu_with_slider::<DamagePercent>(
"Dmg Range (Player)", "Dmg Range (Player)".to_string(),
"save_damage_limits_player", "save_damage_limits_player".to_string(),
"Limits on random damage to apply to the player when loading a save state", "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.0 as u32),
&(menu.save_damage_limits_player.1 as u32), &(menu.save_damage_limits_player.1 as u32),
); );
save_state_tab.add_submenu_with_toggles::<OnOff>( save_state_tab.add_submenu_with_toggles::<OnOff>(
"Enable Save States", "Enable Save States".to_string(),
"save_state_enable", "save_state_enable".to_string(),
"Save States: Enable save states! Save a state with Shield+Down Taunt, load it with Shield+Up Taunt.", "Save States: Enable save states! Save a state with Shield+Down Taunt, load it with Shield+Up Taunt.".to_string(),
true, true,
&(menu.save_state_enable as u32), &(menu.save_state_enable as u32),
); );
save_state_tab.add_submenu_with_toggles::<SaveStateSlot>( save_state_tab.add_submenu_with_toggles::<SaveStateSlot>(
"Save State Slot", "Save State Slot".to_string(),
"save_state_slot", "save_state_slot".to_string(),
"Save State Slot: Save and load states from different slots.", "Save State Slot: Save and load states from different slots.".to_string(),
true, true,
&(menu.save_state_slot as u32), &(menu.save_state_slot as u32),
); );
save_state_tab.add_submenu_with_toggles::<OnOff>( save_state_tab.add_submenu_with_toggles::<OnOff>(
"Randomize Slots", "Randomize Slots".to_string(),
"randomize_slots", "randomize_slots".to_string(),
"Randomize Slots: Randomize slot when loading save state.", "Randomize Slots: Randomize slot when loading save state.".to_string(),
true, true,
&(menu.randomize_slots as u32), &(menu.randomize_slots as u32),
); );
save_state_tab.add_submenu_with_toggles::<CharacterItem>( save_state_tab.add_submenu_with_toggles::<CharacterItem>(
"Character Item", "Character Item".to_string(),
"character_item", "character_item".to_string(),
"Character Item: The item to give to the player's fighter when loading a save state", "Character Item: The item to give to the player's fighter when loading a save state"
.to_string(),
true, true,
&(menu.character_item as u32), &(menu.character_item as u32),
); );
save_state_tab.add_submenu_with_toggles::<BuffOption>( save_state_tab.add_submenu_with_toggles::<BuffOption>(
"Buff Options", "Buff Options".to_string(),
"buff_state", "buff_state".to_string(),
"Buff Options: Buff(s) to be applied to the respective fighters when loading a save state", "Buff Options: Buff(s) to be applied to the respective fighters when loading a save state"
.to_string(),
false, false,
&(menu.buff_state.bits()), &(menu.buff_state.bits()),
); );
save_state_tab.add_submenu_with_toggles::<PlaybackSlot>( save_state_tab.add_submenu_with_toggles::<PlaybackSlot>(
"Save State Playback", "Save State Playback".to_string(),
"save_state_playback", "save_state_playback".to_string(),
"Save State Playback: Choose which slots to playback input recording upon loading a save state", "Save State Playback: Choose which slots to playback input recording upon loading a save state".to_string(),
false, false,
&(menu.save_state_playback.bits() as u32), &(menu.save_state_playback.bits() as u32),
); );
overall_menu.tabs.push(save_state_tab); overall_menu.tabs.push(save_state_tab);
let mut misc_tab = Tab { let mut misc_tab = Tab {
tab_id: "misc", tab_id: "misc".to_string(),
tab_title: "Misc Settings", tab_title: "Misc Settings".to_string(),
tab_submenus: Vec::new(), tab_submenus: Vec::new(),
}; };
misc_tab.add_submenu_with_toggles::<OnOff>( misc_tab.add_submenu_with_toggles::<OnOff>(
"Frame Advantage", "Frame Advantage".to_string(),
"frame_advantage", "frame_advantage".to_string(),
"Frame Advantage: Display the time difference between when the player is actionable and the CPU is actionable", "Frame Advantage: Display the time difference between when the player is actionable and the CPU is actionable".to_string(),
true, true,
&(menu.frame_advantage as u32), &(menu.frame_advantage as u32),
); );
misc_tab.add_submenu_with_toggles::<OnOff>( misc_tab.add_submenu_with_toggles::<OnOff>(
"Hitbox Visualization", "Hitbox Visualization".to_string(),
"hitbox_vis", "hitbox_vis".to_string(),
"Hitbox Visualization: Display a visual representation for active hitboxes (hides other visual effects)", "Hitbox Visualization: Display a visual representation for active hitboxes (hides other visual effects)".to_string(),
true, true,
&(menu.hitbox_vis as u32), &(menu.hitbox_vis as u32),
); );
misc_tab.add_submenu_with_toggles::<Delay>( misc_tab.add_submenu_with_toggles::<Delay>(
"Input Delay", "Input Delay".to_string(),
"input_delay", "input_delay".to_string(),
"Input Delay: Frames to delay player inputs by", "Input Delay: Frames to delay player inputs by".to_string(),
true, true,
&(menu.input_delay.bits()), &(menu.input_delay.bits()),
); );
misc_tab.add_submenu_with_toggles::<OnOff>( misc_tab.add_submenu_with_toggles::<OnOff>(
"Stage Hazards", "Stage Hazards".to_string(),
"stage_hazards", "stage_hazards".to_string(),
"Stage Hazards: Turn stage hazards on/off", "Stage Hazards: Turn stage hazards on/off".to_string(),
true, true,
&(menu.stage_hazards as u32), &(menu.stage_hazards as u32),
); );
misc_tab.add_submenu_with_toggles::<OnOff>( misc_tab.add_submenu_with_toggles::<OnOff>(
"HUD", "HUD".to_string(),
"hud", "hud".to_string(),
"HUD: Show/hide elements of the UI", "HUD: Show/hide elements of the UI".to_string(),
true, true,
&(menu.hud as u32), &(menu.hud as u32),
); );
overall_menu.tabs.push(misc_tab); overall_menu.tabs.push(misc_tab);
let mut input_tab = Tab { let mut input_tab = Tab {
tab_id: "input", tab_id: "input".to_string(),
tab_title: "Input Recording", tab_title: "Input Recording".to_string(),
tab_submenus: Vec::new(), tab_submenus: Vec::new(),
}; };
input_tab.add_submenu_with_toggles::<RecordSlot>( input_tab.add_submenu_with_toggles::<RecordSlot>(
"Recording Slot", "Recording Slot".to_string(),
"recording_slot", "recording_slot".to_string(),
"Recording Slot: Choose which slot to record into", "Recording Slot: Choose which slot to record into".to_string(),
true, true,
&(menu.recording_slot as u32), &(menu.recording_slot as u32),
); );
input_tab.add_submenu_with_toggles::<RecordTrigger>( input_tab.add_submenu_with_toggles::<RecordTrigger>(
"Recording Trigger", "Recording Trigger".to_string(),
"record_trigger", "record_trigger".to_string(),
"Recording Trigger: Whether to begin recording via button combination (Default: Attack+Left Taunt) or upon loading a Save State", format!("Recording Trigger: Whether to begin recording via button combination ({}) or upon loading a Save State", menu.input_record.combination_string()),
false, false,
&(menu.record_trigger.bits() as u32), &(menu.record_trigger.bits() as u32),
); );
input_tab.add_submenu_with_toggles::<RecordingFrames>( input_tab.add_submenu_with_toggles::<RecordingFrames>(
"Recording Frames", "Recording Frames".to_string(),
"recording_frames", "recording_frames".to_string(),
"Recording Frames: Number of frames to record for in the current slot", "Recording Frames: Number of frames to record for in the current slot".to_string(),
true, true,
&(menu.recording_frames as u32), &(menu.recording_frames as u32),
); );
input_tab.add_submenu_with_toggles::<PlaybackSlot>( input_tab.add_submenu_with_toggles::<PlaybackSlot>(
"Playback Button Combination", "Playback Button Combination".to_string(),
"playback_button_combination", "playback_button_combination".to_string(),
"Playback Button Combination: Choose which slots to playback input recording upon pressing button combination (Default: Attack+Right Taunt)", format!("Playback Button Combination: Choose which slots to playback input recording upon pressing button combination ({})", menu.input_playback.combination_string()),
false, false,
&(menu.playback_button_combination.bits() as u32), &(menu.playback_button_combination.bits() as u32),
); );
input_tab.add_submenu_with_toggles::<HitstunPlayback>( input_tab.add_submenu_with_toggles::<HitstunPlayback>(
"Playback Hitstun Timing", "Playback Hitstun Timing".to_string(),
"hitstun_playback", "hitstun_playback".to_string(),
"Playback Hitstun Timing: When to begin playing back inputs when a hitstun mash trigger occurs", "Playback Hitstun Timing: When to begin playing back inputs when a hitstun mash trigger occurs".to_string(),
true, true,
&(menu.hitstun_playback as u32), &(menu.hitstun_playback as u32),
); );
input_tab.add_submenu_with_toggles::<OnOff>( input_tab.add_submenu_with_toggles::<OnOff>(
"Playback Mash Interrupt", "Playback Mash Interrupt".to_string(),
"playback_mash", "playback_mash".to_string(),
"Playback Mash Interrupt: End input playback when a mash trigger occurs", "Playback Mash Interrupt: End input playback when a mash trigger occurs".to_string(),
true, true,
&(menu.playback_mash as u32), &(menu.playback_mash as u32),
); );
input_tab.add_submenu_with_toggles::<OnOff>( input_tab.add_submenu_with_toggles::<OnOff>(
"Playback Loop", "Playback Loop".to_string(),
"playback_loop", "playback_loop".to_string(),
"Playback Loop: Repeat triggered input playbacks indefinitely", "Playback Loop: Repeat triggered input playbacks indefinitely".to_string(),
true, true,
&(menu.playback_loop as u32), &(menu.playback_loop as u32),
); );
input_tab.add_submenu_with_toggles::<OnOff>( input_tab.add_submenu_with_toggles::<OnOff>(
"Recording Crop", "Recording Crop".to_string(),
"recording_crop", "recording_crop".to_string(),
"Recording Crop: Remove neutral input frames at the end of your recording", "Recording Crop: Remove neutral input frames at the end of your recording".to_string(),
true, true,
&(menu.recording_crop as u32), &(menu.recording_crop as u32),
); );
overall_menu.tabs.push(input_tab); overall_menu.tabs.push(input_tab);
let mut button_tab = Tab { let mut button_tab = Tab {
tab_id: "button", tab_id: "button".to_string(),
tab_title: "Button Config", tab_title: "Button Config".to_string(),
tab_submenus: Vec::new(), tab_submenus: Vec::new(),
}; };
button_tab.add_submenu_with_toggles::<ButtonConfig>( button_tab.add_submenu_with_toggles::<ButtonConfig>(
"Menu Open", "Menu Open".to_string(),
"menu_open", "menu_open".to_string(),
"Menu Open: Hold: Hold any one button and press the others to trigger", "Menu Open: Hold: Hold any one button and press the others to trigger".to_string(),
false, false,
&(menu.menu_open.bits() as u32), &(menu.menu_open.bits() as u32),
); );
button_tab.add_submenu_with_toggles::<ButtonConfig>( button_tab.add_submenu_with_toggles::<ButtonConfig>(
"Save State Save", "Save State Save".to_string(),
"save_state_save", "save_state_save".to_string(),
"Save State Save: Hold any one button and press the others to trigger", "Save State Save: Hold any one button and press the others to trigger".to_string(),
false, false,
&(menu.save_state_save.bits() as u32), &(menu.save_state_save.bits() as u32),
); );
button_tab.add_submenu_with_toggles::<ButtonConfig>( button_tab.add_submenu_with_toggles::<ButtonConfig>(
"Save State Load", "Save State Load".to_string(),
"save_state_load", "save_state_load".to_string(),
"Save State Load: Hold any one button and press the others to trigger", "Save State Load: Hold any one button and press the others to trigger".to_string(),
false, false,
&(menu.save_state_load.bits() as u32), &(menu.save_state_load.bits() as u32),
); );
button_tab.add_submenu_with_toggles::<ButtonConfig>( button_tab.add_submenu_with_toggles::<ButtonConfig>(
"Input Record", "Input Record".to_string(),
"input_record", "input_record".to_string(),
"Input Record: Hold any one button and press the others to trigger", "Input Record: Hold any one button and press the others to trigger".to_string(),
false, false,
&(menu.input_record.bits() as u32), &(menu.input_record.bits() as u32),
); );
button_tab.add_submenu_with_toggles::<ButtonConfig>( button_tab.add_submenu_with_toggles::<ButtonConfig>(
"Input Playback", "Input Playback".to_string(),
"input_playback", "input_playback".to_string(),
"Input Playback: Hold any one button and press the others to trigger", "Input Playback: Hold any one button and press the others to trigger".to_string(),
false, false,
&(menu.input_playback.bits() as u32), &(menu.input_playback.bits() as u32),
); );

View file

@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
#[cfg(feature = "smash")] #[cfg(feature = "smash")]
use smash::lib::lua_const::*; use smash::lib::lua_const::*;
use std::fmt;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumIter; use strum_macros::EnumIter;
@ -21,6 +22,7 @@ fn log_2(x: u32) -> u32 {
pub trait ToggleTrait { pub trait ToggleTrait {
fn to_toggle_strs() -> Vec<&'static str>; fn to_toggle_strs() -> Vec<&'static str>;
fn to_toggle_vals() -> Vec<u32>; fn to_toggle_vals() -> Vec<u32>;
fn to_toggle_strings() -> Vec<String>;
} }
pub trait SliderTrait { pub trait SliderTrait {
@ -81,6 +83,11 @@ macro_rules! extra_bitflag_impls {
let all_options = <$e>::all().to_vec(); let all_options = <$e>::all().to_vec();
all_options.iter().map(|i| i.bits() as u32).collect() 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 { impl ToggleTrait for Shield {
fn to_toggle_strs() -> Vec<&'static str> { fn to_toggle_strs() -> Vec<&'static str> {
Shield::iter().map(|i| i.as_str().unwrap_or("")).collect() Shield::iter().map(|i| i.as_str().unwrap_or("")).collect()
@ -354,6 +367,9 @@ impl ToggleTrait for Shield {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
Shield::iter().map(|i| i as u32).collect() 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 // 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 { impl ToggleTrait for SaveStateMirroring {
fn to_toggle_strs() -> Vec<&'static str> { fn to_toggle_strs() -> Vec<&'static str> {
SaveStateMirroring::iter() SaveStateMirroring::iter()
@ -387,6 +409,10 @@ impl ToggleTrait for SaveStateMirroring {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
SaveStateMirroring::iter().map(|i| i as u32).collect() SaveStateMirroring::iter().map(|i| i as u32).collect()
} }
fn to_toggle_strings() -> Vec<String> {
SaveStateMirroring::iter().map(|i| i.to_string()).collect()
}
} }
#[repr(i32)] #[repr(i32)]
@ -420,6 +446,9 @@ impl ToggleTrait for OnOff {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
vec![0, 1] vec![0, 1]
} }
fn to_toggle_strings() -> Vec<String> {
vec!["Off".to_string(), "On".to_string()]
}
} }
bitflags! { bitflags! {
@ -1006,6 +1035,16 @@ impl ToggleTrait for SdiFrequency {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
SdiFrequency::iter().map(|i| i as u32).collect() 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)] #[repr(u32)]
@ -1049,6 +1088,16 @@ impl ToggleTrait for ClatterFrequency {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
ClatterFrequency::iter().map(|i| i as u32).collect() 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 /// Item Selections
@ -1114,6 +1163,16 @@ impl ToggleTrait for CharacterItem {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
CharacterItem::iter().map(|i| i as u32).collect() 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! { bitflags! {
@ -1251,6 +1310,16 @@ impl ToggleTrait for SaveStateSlot {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
SaveStateSlot::iter().map(|i| i as u32).collect() 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 // Input Recording Slot
@ -1298,6 +1367,16 @@ impl ToggleTrait for RecordSlot {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
RecordSlot::iter().map(|i| i as u32).collect() 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 // Input Playback Slot
@ -1371,6 +1450,16 @@ impl ToggleTrait for HitstunPlayback {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
HitstunPlayback::iter().map(|i| i as u32).collect() 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 // Input Recording Trigger Type
@ -1462,6 +1551,16 @@ impl ToggleTrait for RecordingFrames {
fn to_toggle_vals() -> Vec<u32> { fn to_toggle_vals() -> Vec<u32> {
RecordingFrames::iter().map(|i| i as u32).collect() 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! { bitflags! {
@ -1508,6 +1607,13 @@ impl ButtonConfig {
_ => return None, _ => 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} extra_bitflag_impls! {ButtonConfig}

View file

@ -35,27 +35,29 @@ pub enum AppPage {
/// We should hold a list of SubMenus. /// We should hold a list of SubMenus.
/// The currently selected SubMenu should also have an associated list with necessary information. /// The currently selected SubMenu should also have an associated list with necessary information.
/// We can convert the option types (Toggle, OnOff, Slider) to lists /// We can convert the option types (Toggle, OnOff, Slider) to lists
pub struct App<'a> { pub struct App {
pub tabs: StatefulList<&'a str>, pub tabs: StatefulList<String>,
pub menu_items: HashMap<&'a str, MultiStatefulList<SubMenu<'a>>>, pub menu_items: HashMap<String, MultiStatefulList<SubMenu>>,
pub selected_sub_menu_toggles: MultiStatefulList<Toggle<'a>>, pub selected_sub_menu_toggles: MultiStatefulList<Toggle>,
pub selected_sub_menu_slider: DoubleEndedGauge, pub selected_sub_menu_slider: DoubleEndedGauge,
pub page: AppPage, pub page: AppPage,
pub default_menu: (UiMenu<'a>, String), pub default_menu: (UiMenu, String),
} }
impl<'a> App<'a> { impl<'a> App {
pub fn new(menu: UiMenu<'a>, default_menu: (UiMenu<'a>, String)) -> App<'a> { pub fn new(menu: UiMenu, default_menu: (UiMenu, String)) -> App {
let mut menu_items_stateful = HashMap::new(); let mut menu_items_stateful = HashMap::new();
menu.tabs.iter().for_each(|tab| { menu.tabs.iter().for_each(|tab| {
menu_items_stateful.insert( menu_items_stateful.insert(
tab.tab_title, tab.tab_title.clone(),
MultiStatefulList::with_items(tab.tab_submenus.clone(), NUM_LISTS), MultiStatefulList::with_items(tab.tab_submenus.clone(), NUM_LISTS),
); );
}); });
let mut app = App { 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, menu_items: menu_items_stateful,
selected_sub_menu_toggles: MultiStatefulList::with_items(vec![], 0), selected_sub_menu_toggles: MultiStatefulList::with_items(vec![], 0),
selected_sub_menu_slider: DoubleEndedGauge::new(), selected_sub_menu_slider: DoubleEndedGauge::new(),
@ -82,7 +84,7 @@ impl<'a> App<'a> {
let toggles = selected_sub_menu.toggles.clone(); let toggles = selected_sub_menu.toggles.clone();
let slider = selected_sub_menu.slider.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 => { SubMenuType::TOGGLE => {
self.selected_sub_menu_toggles = MultiStatefulList::with_items( self.selected_sub_menu_toggles = MultiStatefulList::with_items(
toggles, toggles,
@ -117,13 +119,13 @@ impl<'a> App<'a> {
/// Returns the currently selected SubMenu struct /// Returns the currently selected SubMenu struct
/// ///
/// { /// {
/// submenu_title: &'a str, /// submenu_title: String,
/// submenu_id: &'a str, /// submenu_id: String,
/// help_text: &'a str, /// help_text: String,
/// is_single_option: bool, /// is_single_option: bool,
/// toggles: Vec<Toggle<'a>>, /// toggles: Vec<Toggle<'a>>,
/// slider: Option<Slider>, /// slider: Option<Slider>,
/// _type: &'a str, /// _type: String,
/// } /// }
fn sub_menu_selected(&self) -> &SubMenu { fn sub_menu_selected(&self) -> &SubMenu {
let (list_section, list_idx) = self let (list_section, list_idx) = self
@ -141,7 +143,7 @@ impl<'a> App<'a> {
/// Toggles: calls next() /// Toggles: calls next()
/// Slider: Swaps between MinHover and MaxHover /// Slider: Swaps between MinHover and MaxHover
pub fn sub_menu_next(&mut self) { 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::TOGGLE => self.selected_sub_menu_toggles.next(),
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state { SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover, GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover,
@ -157,7 +159,7 @@ impl<'a> App<'a> {
/// * Swaps between MinHover and MaxHover /// * Swaps between MinHover and MaxHover
/// * Increments the selected_min/max if possible /// * Increments the selected_min/max if possible
pub fn sub_menu_next_list(&mut self) { 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::TOGGLE => self.selected_sub_menu_toggles.next_list(),
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state { SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover, GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover,
@ -185,7 +187,7 @@ impl<'a> App<'a> {
/// Toggles: calls previous() /// Toggles: calls previous()
/// Slider: Swaps between MinHover and MaxHover /// Slider: Swaps between MinHover and MaxHover
pub fn sub_menu_previous(&mut self) { 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::TOGGLE => self.selected_sub_menu_toggles.previous(),
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state { SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover, GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover,
@ -201,7 +203,7 @@ impl<'a> App<'a> {
/// * Swaps between MinHover and MaxHover /// * Swaps between MinHover and MaxHover
/// * Decrements the selected_min/max if possible /// * Decrements the selected_min/max if possible
pub fn sub_menu_previous_list(&mut self) { 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::TOGGLE => self.selected_sub_menu_toggles.previous_list(),
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state { SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover, 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 /// 2: Vec(toggle checked, title) for toggles, Vec(nothing) for slider
/// 3: ListState for toggles, ListState::new() for slider /// 3: ListState for toggles, ListState::new() for slider
/// TODO: Refactor return type into a nice struct /// 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().submenu_title.clone(),
self.sub_menu_selected().help_text, self.sub_menu_selected().help_text.clone(),
match SubMenuType::from_str(self.sub_menu_selected()._type) { match SubMenuType::from_string(&self.sub_menu_selected()._type) {
SubMenuType::TOGGLE => self SubMenuType::TOGGLE => self
.selected_sub_menu_toggles .selected_sub_menu_toggles
.lists .lists
@ -246,7 +250,7 @@ impl<'a> App<'a> {
toggle_list toggle_list
.items .items
.iter() .iter()
.map(|toggle| (toggle.checked, toggle.toggle_title)) .map(|toggle| (toggle.checked, toggle.toggle_title.clone()))
.collect(), .collect(),
toggle_list.state.clone(), toggle_list.state.clone(),
) )
@ -264,16 +268,16 @@ impl<'a> App<'a> {
/// 1: Help text /// 1: Help text
/// 2: Reference to self.selected_sub_menu_slider /// 2: Reference to self.selected_sub_menu_slider
/// TODO: Refactor return type into a nice struct /// TODO: Refactor return type into a nice struct
pub fn sub_menu_strs_for_slider(&self) -> (&str, &str, &DoubleEndedGauge) { pub fn sub_menu_strs_for_slider(&self) -> (String, String, &DoubleEndedGauge) {
let slider = match SubMenuType::from_str(self.sub_menu_selected()._type) { let slider = match SubMenuType::from_string(&self.sub_menu_selected()._type) {
SubMenuType::SLIDER => &self.selected_sub_menu_slider, SubMenuType::SLIDER => &self.selected_sub_menu_slider,
_ => { _ => {
panic!("Slider not selected!"); panic!("Slider not selected!");
} }
}; };
( (
self.sub_menu_selected().submenu_title, self.sub_menu_selected().submenu_title.clone(),
self.sub_menu_selected().help_text, self.sub_menu_selected().help_text.clone(),
slider, slider,
) )
} }
@ -298,7 +302,7 @@ impl<'a> App<'a> {
.get_mut(list_idx) .get_mut(list_idx)
.unwrap(); .unwrap();
if self.page == AppPage::SUBMENU { 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 // Need to change the slider state to MinHover so the slider shows up initially
SubMenuType::SLIDER => { SubMenuType::SLIDER => {
self.page = AppPage::SLIDER; self.page = AppPage::SLIDER;
@ -307,7 +311,7 @@ impl<'a> App<'a> {
SubMenuType::TOGGLE => self.page = AppPage::TOGGLE, SubMenuType::TOGGLE => self.page = AppPage::TOGGLE,
} }
} else { } else {
match SubMenuType::from_str(selected_sub_menu._type) { match SubMenuType::from_string(&selected_sub_menu._type) {
SubMenuType::TOGGLE => { SubMenuType::TOGGLE => {
let is_single_option = selected_sub_menu.is_single_option; let is_single_option = selected_sub_menu.is_single_option;
let state = self.selected_sub_menu_toggles.state; let state = self.selected_sub_menu_toggles.state;
@ -404,7 +408,7 @@ impl<'a> App<'a> {
.items .items
.get_mut(list_idx) .get_mut(list_idx)
.unwrap(); .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 { SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
GaugeState::MinSelected => { GaugeState::MinSelected => {
self.selected_sub_menu_slider.state = GaugeState::MinHover; self.selected_sub_menu_slider.state = GaugeState::MinHover;
@ -455,10 +459,11 @@ impl<'a> App<'a> {
let json = self.to_json(); let json = self.to_json();
let mut json_value = serde_json::from_str::<serde_json::Value>(&json).unwrap(); let mut json_value = serde_json::from_str::<serde_json::Value>(&json).unwrap();
let selected_sub_menu = self.sub_menu_selected(); 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 = let default_json_value =
serde_json::from_str::<serde_json::Value>(&self.default_menu.1).unwrap(); 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(); let new_menu = serde_json::from_value::<TrainingModpackMenu>(json_value).unwrap();
*self = App::new(unsafe { ui_menu(new_menu) }, self.default_menu.clone()); *self = App::new(unsafe { ui_menu(new_menu) }, self.default_menu.clone());
} }
@ -587,7 +592,7 @@ impl<'a> App<'a> {
.unwrap() .unwrap()
} }
pub fn submenu_ids(&self) -> Vec<&str> { pub fn submenu_ids(&self) -> Vec<String> {
return self return self
.menu_items .menu_items
.values() .values()
@ -599,10 +604,10 @@ impl<'a> App<'a> {
sub_stateful_list sub_stateful_list
.items .items
.iter() .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() .iter()
.map(|i| { .map(|i| {
let lines = vec![Spans::from(if stateful_list.state.selected().is_some() { let lines = vec![Spans::from(if stateful_list.state.selected().is_some() {
i.submenu_title.to_owned() i.submenu_title.clone()
} else { } else {
" ".to_owned() + i.submenu_title format!(" {}", i.submenu_title.clone())
})]; })];
ListItem::new(lines).style(Style::default().fg(Color::White)) 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(); let mut state = stateful_list.state.clone();
if state.selected().is_some() { 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); f.render_stateful_widget(list, list_chunks[list_section], &mut state);
} }
let help_paragraph = Paragraph::new( 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", + "\nZL/ZR: Next tab | X: Save Defaults | R: Reset All Menus",
) )
.style(Style::default().fg(Color::Cyan)); .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 let values_items: Vec<ListItem> = sub_menu_str
.iter() .iter()
.map(|s| { .map(|s| {
ListItem::new(vec![Spans::from( ListItem::new(vec![Spans::from(if s.0 {
(if s.0 { "X " } else { " " }).to_owned() + s.1, format!("X {}", s.1)
)]) } else {
format!(" {}", s.1)
})])
}) })
.collect(); .collect();
let values_list = List::new(values_items) 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) .start_corner(Corner::TopLeft)
.highlight_style( .highlight_style(
Style::default() Style::default()
@ -795,10 +810,10 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
.enumerate() .enumerate()
.map(|(idx, tab)| { .map(|(idx, tab)| {
if idx == tab_selected { if idx == tab_selected {
span_selected = Spans::from("> ".to_owned() + tab); span_selected = Spans::from(format!("> {}", tab));
Spans::from("> ".to_owned() + tab) Spans::from(format!("> {}", tab))
} else { } else {
Spans::from(" ".to_owned() + tab) Spans::from(format!(" {}", tab))
} }
}) })
.collect(); .collect();

View file

@ -17,17 +17,17 @@ use tui::Terminal;
use training_mod_consts::*; use training_mod_consts::*;
fn test_backend_setup<'a>( fn test_backend_setup(
ui_menu: UiMenu<'a>, ui_menu: UiMenu,
menu_defaults: (UiMenu<'a>, String), menu_defaults: (UiMenu, String),
) -> Result< ) -> Result<
( (
Terminal<training_mod_tui::TestBackend>, Terminal<training_mod_tui::TestBackend>,
training_mod_tui::App<'a>, training_mod_tui::App,
), ),
Box<dyn Error>, 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 backend = tui::backend::TestBackend::new(120, 15);
let terminal = Terminal::new(backend)?; let terminal = Terminal::new(backend)?;
let mut state = tui::widgets::ListState::default(); let mut state = tui::widgets::ListState::default();