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

Change Menu Buttons and Help Text; HUD Off Outside Training (#488)

* Avoid making layouts invisible outside training mode with HUD off

* Switch menu buttons around; change defaults resetting texts

* Fix unused feature import
This commit is contained in:
jugeeya 2023-02-20 21:45:33 -08:00 committed by GitHub
parent 7fb93ea309
commit d05f8ce918
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 206 additions and 158 deletions

View file

@ -80,6 +80,7 @@ pub struct ButtonPresses {
pub a: ButtonPress, pub a: ButtonPress,
pub b: ButtonPress, pub b: ButtonPress,
pub x: ButtonPress, pub x: ButtonPress,
pub y: ButtonPress,
pub r: ButtonPress, pub r: ButtonPress,
pub l: ButtonPress, pub l: ButtonPress,
pub zr: ButtonPress, pub zr: ButtonPress,
@ -133,6 +134,11 @@ pub static mut BUTTON_PRESSES: ButtonPresses = ButtonPresses {
is_pressed: false, is_pressed: false,
lockout_frames: 0, lockout_frames: 0,
}, },
y: ButtonPress {
prev_frame_is_pressed: false,
is_pressed: false,
lockout_frames: 0,
},
r: ButtonPress { r: ButtonPress {
prev_frame_is_pressed: false, prev_frame_is_pressed: false,
is_pressed: false, is_pressed: false,
@ -180,7 +186,6 @@ pub fn handle_get_npad_state(state: *mut NpadGcState, _controller_id: *const u32
let update_count = (*state).updateCount; let update_count = (*state).updateCount;
let flags = (*state).Flags; let flags = (*state).Flags;
if QUICK_MENU_ACTIVE { if QUICK_MENU_ACTIVE {
if (*state).Buttons & (1 << 0) > 0 { if (*state).Buttons & (1 << 0) > 0 {
BUTTON_PRESSES.a.is_pressed = true; BUTTON_PRESSES.a.is_pressed = true;
} }
@ -190,6 +195,9 @@ pub fn handle_get_npad_state(state: *mut NpadGcState, _controller_id: *const u32
if (*state).Buttons & (1 << 2) > 0 { if (*state).Buttons & (1 << 2) > 0 {
BUTTON_PRESSES.x.is_pressed = true; BUTTON_PRESSES.x.is_pressed = true;
} }
if (*state).Buttons & (1 << 3) > 0 {
BUTTON_PRESSES.y.is_pressed = true;
}
if (*state).Buttons & (1 << 6) > 0 { if (*state).Buttons & (1 << 6) > 0 {
BUTTON_PRESSES.l.is_pressed = true; BUTTON_PRESSES.l.is_pressed = true;
} }
@ -239,6 +247,12 @@ lazy_static! {
); );
} }
pub unsafe fn p1_controller_is_gcc() -> bool {
let p1_controller_id = crate::training::input_delay::p1_controller_id();
let p1_style_set = GetNpadStyleSet(&p1_controller_id as *const _);
(p1_style_set.flags & (1 << 5)) > 0
}
pub unsafe fn quick_menu_loop() { pub unsafe fn quick_menu_loop() {
loop { loop {
std::thread::sleep(std::time::Duration::from_secs(10)); std::thread::sleep(std::time::Duration::from_secs(10));
@ -262,6 +276,8 @@ pub unsafe fn quick_menu_loop() {
continue; continue;
} }
let is_gcc = p1_controller_is_gcc();
let app = &mut *QUICK_MENU_APP.data_ptr(); let app = &mut *QUICK_MENU_APP.data_ptr();
button_presses.a.read_press().then(|| { button_presses.a.read_press().then(|| {
app.on_a(); app.on_a();
@ -282,23 +298,39 @@ pub unsafe fn quick_menu_loop() {
} }
}); });
button_presses.x.read_press().then(|| { button_presses.x.read_press().then(|| {
app.on_x(); app.save_defaults();
received_input = true;
});
button_presses.y.read_press().then(|| {
app.reset_current_submenu();
received_input = true; received_input = true;
}); });
button_presses.l.read_press().then(|| { button_presses.l.read_press().then(|| {
app.on_l(); if is_gcc {
app.previous_tab();
}
received_input = true; received_input = true;
}); });
button_presses.r.read_press().then(|| { button_presses.r.read_press().then(|| {
app.on_r(); if is_gcc {
app.next_tab();
} else {
app.reset_all_submenus();
}
received_input = true; received_input = true;
}); });
button_presses.zl.read_press().then(|| { button_presses.zl.read_press().then(|| {
app.on_zl(); if !is_gcc {
app.previous_tab();
}
received_input = true; received_input = true;
}); });
button_presses.zr.read_press().then(|| { button_presses.zr.read_press().then(|| {
app.on_zr(); if !is_gcc {
app.next_tab();
} else {
app.reset_all_submenus();
}
received_input = true; received_input = true;
}); });
button_presses.left.read_press().then(|| { button_presses.left.read_press().then(|| {

View file

@ -1,7 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use skyline::nn::hid::GetNpadStyleSet;
use skyline::nn::ui2d::*; use skyline::nn::ui2d::*;
use smash::ui2d::{SmashPane, SmashTextBox}; use smash::ui2d::{SmashPane, SmashTextBox};
use training_mod_tui::gauge::GaugeState; use training_mod_tui::gauge::GaugeState;
@ -55,12 +54,18 @@ const BG_LEFT_SELECTED_WHITE_COLOR: ResColor = ResColor {
}; };
lazy_static! { lazy_static! {
static ref GCC_BUTTON_MAPPING: HashMap<&'static str, u16> = static ref GCC_BUTTON_MAPPING: HashMap<&'static str, u16> = HashMap::from([
HashMap::from([("L", 0xE204), ("R", 0xE205), ("X", 0xE206), ("Z", 0xE208)]); ("L", 0xE204),
("R", 0xE205),
("X", 0xE206),
("Y", 0xE207),
("Z", 0xE208)
]);
static ref PROCON_BUTTON_MAPPING: HashMap<&'static str, u16> = HashMap::from([ static ref PROCON_BUTTON_MAPPING: HashMap<&'static str, u16> = HashMap::from([
("L", 0xE0E4), ("L", 0xE0E4),
("R", 0xE0E5), ("R", 0xE0E5),
("X", 0xE0E2), ("X", 0xE0E2),
("Y", 0xE0E3),
("ZL", 0xE0E6), ("ZL", 0xE0E6),
("ZR", 0xE0E7) ("ZR", 0xE0E7)
]); ]);
@ -194,11 +199,15 @@ unsafe fn render_toggle_page(app: &App, root_pane: &mut Pane) {
}); });
title_text.set_text_string(name); title_text.set_text_string(name);
menu_button.find_pane_by_name_recursive("check") menu_button
.find_pane_by_name_recursive("check")
.unwrap() .unwrap()
.set_visible(true); .set_visible(true);
menu_button.find_pane_by_name_recursive("Icon").unwrap().set_visible(*checked); menu_button
.find_pane_by_name_recursive("Icon")
.unwrap()
.set_visible(*checked);
let title_bg_material = &mut *title_bg.material; let title_bg_material = &mut *title_bg.material;
@ -403,17 +412,16 @@ pub unsafe fn draw(root_pane: &mut Pane) {
}; };
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]);
let p1_controller_id = crate::training::input_delay::p1_controller_id(); let is_gcc = common::menu::p1_controller_is_gcc();
let p1_style_set = GetNpadStyleSet(&p1_controller_id as *const _);
let is_gcc = (p1_style_set.flags & (1 << 5)) > 0;
let button_mapping = if is_gcc { let button_mapping = if is_gcc {
GCC_BUTTON_MAPPING.clone() GCC_BUTTON_MAPPING.clone()
} else { } else {
PROCON_BUTTON_MAPPING.clone() PROCON_BUTTON_MAPPING.clone()
}; };
let (x_key, l_key, r_key, zl_key, zr_key, z_key) = ( let (x_key, y_key, l_key, r_key, zl_key, zr_key, z_key) = (
button_mapping.get("X"), button_mapping.get("X"),
button_mapping.get("Y"),
button_mapping.get("L"), button_mapping.get("L"),
button_mapping.get("R"), button_mapping.get("R"),
button_mapping.get("ZL"), button_mapping.get("ZL"),
@ -423,9 +431,9 @@ pub unsafe fn draw(root_pane: &mut Pane) {
let (left_tab_key, right_tab_key, save_defaults_key, reset_current_key, reset_all_key) = let (left_tab_key, right_tab_key, save_defaults_key, reset_current_key, reset_all_key) =
if is_gcc { if is_gcc {
(None, z_key, x_key, l_key, r_key) (l_key, r_key, x_key, y_key, z_key)
} else { } else {
(zl_key, zr_key, x_key, l_key, r_key) (zl_key, zr_key, x_key, y_key, r_key)
}; };
[ [
@ -465,12 +473,12 @@ pub unsafe fn draw(root_pane: &mut Pane) {
help_pane.set_text_string(tab_titles[idx]); help_pane.set_text_string(tab_titles[idx]);
}); });
[ [
(save_defaults_key, "SaveDefaults"), (save_defaults_key, "SaveDefaults", "Save Defaults"),
(reset_current_key, "ResetCurrentDefaults"), (reset_current_key, "ResetCurrentDefaults", "Reset Current"),
(reset_all_key, "ResetAllDefaults"), (reset_all_key, "ResetAllDefaults", "Reset All"),
] ]
.iter() .iter()
.for_each(|(key, name)| { .for_each(|(key, name, title)| {
let key_help_pane = root_pane.find_pane_by_name_recursive(name).unwrap(); let key_help_pane = root_pane.find_pane_by_name_recursive(name).unwrap();
let icon_pane = key_help_pane let icon_pane = key_help_pane
@ -483,26 +491,11 @@ pub unsafe fn draw(root_pane: &mut Pane) {
*it = *key.unwrap(); *it = *key.unwrap();
*(it.add(1)) = 0x0; *(it.add(1)) = 0x0;
// PascalCase to Title Case
let title_case = name
.chars()
.fold(vec![], |mut acc, ch| {
if ch.is_uppercase() {
acc.push(String::new());
}
if let Some(last) = acc.last_mut() {
last.push(ch);
}
acc
})
.into_iter()
.collect::<Vec<String>>()
.join(" ");
key_help_pane key_help_pane
.find_pane_by_name_recursive("set_txt_help") .find_pane_by_name_recursive("set_txt_help")
.unwrap() .unwrap()
.as_textbox() .as_textbox()
.set_text_string(title_case.as_str()); .set_text_string(title);
}); });
match app.page { match app.page {

View file

@ -1,3 +1,4 @@
#[cfg(feature = "layout_arc_from_file")]
use byte_unit::MEBIBYTE; use byte_unit::MEBIBYTE;
use sarc::SarcFile; use sarc::SarcFile;
use skyline::nn::ui2d::*; use skyline::nn::ui2d::*;
@ -22,6 +23,8 @@ pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64)
// InfluencedAlpha means "Should my children panes' alpha be influenced by mine, as the parent?" // InfluencedAlpha means "Should my children panes' alpha be influenced by mine, as the parent?"
root_pane.flags |= 1 << PaneFlag::InfluencedAlpha as u8; root_pane.flags |= 1 << PaneFlag::InfluencedAlpha as u8;
root_pane.set_visible(MENU.hud == OnOff::On); root_pane.set_visible(MENU.hud == OnOff::On);
} else {
root_pane.set_visible(true);
} }
damage::draw(root_pane, layout_name); damage::draw(root_pane, layout_name);

View file

@ -1,4 +1,6 @@
use training_mod_consts::{MenuJsonStruct, Slider, SubMenu, SubMenuType, Toggle, UiMenu, ui_menu, TrainingModpackMenu}; use training_mod_consts::{
ui_menu, MenuJsonStruct, Slider, SubMenu, SubMenuType, Toggle, TrainingModpackMenu, UiMenu,
};
use tui::{ use tui::{
backend::Backend, backend::Backend,
layout::{Constraint, Corner, Direction, Layout, Rect}, layout::{Constraint, Corner, Direction, Layout, Rect},
@ -8,8 +10,8 @@ use tui::{
Frame, Frame,
}; };
use serde_json::{json, Map};
use std::collections::HashMap; use std::collections::HashMap;
use serde_json::{Map, json};
pub use tui::{backend::TestBackend, style::Color, Terminal}; pub use tui::{backend::TestBackend, style::Color, Terminal};
pub mod gauge; pub mod gauge;
@ -25,7 +27,7 @@ pub enum AppPage {
SUBMENU, SUBMENU,
TOGGLE, TOGGLE,
SLIDER, SLIDER,
CONFIRMATION CONFIRMATION,
} }
/// We should hold a list of SubMenus. /// We should hold a list of SubMenus.
@ -58,7 +60,7 @@ impl<'a> App<'a> {
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(),
page: AppPage::SUBMENU, page: AppPage::SUBMENU,
default_menu: default_menu default_menu: default_menu,
}; };
app.set_sub_menu_items(); app.set_sub_menu_items();
app app
@ -230,9 +232,7 @@ 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( pub fn sub_menu_strs_and_states(&self) -> (&str, &str, Vec<(Vec<(bool, &str)>, ListState)>) {
&self,
) -> (&str, &str, Vec<(Vec<(bool, &str)>, ListState)>) {
( (
self.sub_menu_selected().submenu_title, self.sub_menu_selected().submenu_title,
self.sub_menu_selected().help_text, self.sub_menu_selected().help_text,
@ -293,8 +293,7 @@ impl<'a> App<'a> {
.get(tab_selected) .get(tab_selected)
.unwrap() .unwrap()
.idx_to_list_idx(self.menu_items.get(tab_selected).unwrap().state); .idx_to_list_idx(self.menu_items.get(tab_selected).unwrap().state);
let selected_sub_menu = self.menu_items.get_mut(tab_selected).unwrap().lists let selected_sub_menu = self.menu_items.get_mut(tab_selected).unwrap().lists[list_section]
[list_section]
.items .items
.get_mut(list_idx) .get_mut(list_idx)
.unwrap(); .unwrap();
@ -305,7 +304,7 @@ impl<'a> App<'a> {
self.page = AppPage::SLIDER; self.page = AppPage::SLIDER;
self.selected_sub_menu_slider.state = GaugeState::MinHover; self.selected_sub_menu_slider.state = GaugeState::MinHover;
} }
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_str(selected_sub_menu._type) {
@ -323,7 +322,9 @@ impl<'a> App<'a> {
if !o.checked { if !o.checked {
o.checked = true; o.checked = true;
} else { } else {
if is_single_option { return; } if is_single_option {
return;
}
o.checked = false; o.checked = false;
} }
} else if is_single_option { } else if is_single_option {
@ -341,7 +342,9 @@ impl<'a> App<'a> {
if !o.checked { if !o.checked {
o.checked = true; o.checked = true;
} else { } else {
if is_single_option { return; } if is_single_option {
return;
}
o.checked = false; o.checked = false;
} }
} else if is_single_option { } else if is_single_option {
@ -358,7 +361,7 @@ impl<'a> App<'a> {
} }
GaugeState::MinSelected => { GaugeState::MinSelected => {
self.selected_sub_menu_slider.state = GaugeState::MinHover; self.selected_sub_menu_slider.state = GaugeState::MinHover;
selected_sub_menu.slider = Some(Slider{ selected_sub_menu.slider = Some(Slider {
selected_min: self.selected_sub_menu_slider.selected_min, selected_min: self.selected_sub_menu_slider.selected_min,
selected_max: self.selected_sub_menu_slider.selected_max, selected_max: self.selected_sub_menu_slider.selected_max,
abs_min: self.selected_sub_menu_slider.abs_min, abs_min: self.selected_sub_menu_slider.abs_min,
@ -367,7 +370,7 @@ impl<'a> App<'a> {
} }
GaugeState::MaxSelected => { GaugeState::MaxSelected => {
self.selected_sub_menu_slider.state = GaugeState::MaxHover; self.selected_sub_menu_slider.state = GaugeState::MaxHover;
selected_sub_menu.slider = Some(Slider{ selected_sub_menu.slider = Some(Slider {
selected_min: self.selected_sub_menu_slider.selected_min, selected_min: self.selected_sub_menu_slider.selected_min,
selected_max: self.selected_sub_menu_slider.selected_max, selected_max: self.selected_sub_menu_slider.selected_max,
abs_min: self.selected_sub_menu_slider.abs_min, abs_min: self.selected_sub_menu_slider.abs_min,
@ -405,7 +408,7 @@ impl<'a> App<'a> {
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;
selected_sub_menu.slider = Some(Slider{ selected_sub_menu.slider = Some(Slider {
selected_min: self.selected_sub_menu_slider.selected_min, selected_min: self.selected_sub_menu_slider.selected_min,
selected_max: self.selected_sub_menu_slider.selected_max, selected_max: self.selected_sub_menu_slider.selected_max,
abs_min: self.selected_sub_menu_slider.abs_min, abs_min: self.selected_sub_menu_slider.abs_min,
@ -416,7 +419,7 @@ impl<'a> App<'a> {
} }
GaugeState::MaxSelected => { GaugeState::MaxSelected => {
self.selected_sub_menu_slider.state = GaugeState::MaxHover; self.selected_sub_menu_slider.state = GaugeState::MaxHover;
selected_sub_menu.slider = Some(Slider{ selected_sub_menu.slider = Some(Slider {
selected_min: self.selected_sub_menu_slider.selected_min, selected_min: self.selected_sub_menu_slider.selected_min,
selected_max: self.selected_sub_menu_slider.selected_max, selected_max: self.selected_sub_menu_slider.selected_max,
abs_min: self.selected_sub_menu_slider.abs_min, abs_min: self.selected_sub_menu_slider.abs_min,
@ -434,23 +437,27 @@ impl<'a> App<'a> {
} }
/// Save defaults command /// Save defaults command
pub fn on_x(&mut self) { pub fn save_defaults(&mut self) {
if self.page == AppPage::SUBMENU { if self.page == AppPage::SUBMENU {
let json = self.to_json(); let json = self.to_json();
unsafe { unsafe {
self.default_menu = (ui_menu(serde_json::from_str::<TrainingModpackMenu>(&json).unwrap()), json); self.default_menu = (
ui_menu(serde_json::from_str::<TrainingModpackMenu>(&json).unwrap()),
json,
);
} }
} }
} }
/// Reset current submenu to defaults /// Reset current submenu to defaults
pub fn on_l(&mut self) { pub fn reset_current_submenu(&mut self) {
if self.page == AppPage::TOGGLE || self.page == AppPage::SLIDER { if self.page == AppPage::TOGGLE || self.page == AppPage::SLIDER {
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;
let default_json_value = serde_json::from_str::<serde_json::Value>(&self.default_menu.1).unwrap(); 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).unwrap() = default_json_value.get(id).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());
@ -458,18 +465,18 @@ impl<'a> App<'a> {
} }
/// Reset all menus to defaults /// Reset all menus to defaults
pub fn on_r(&mut self) { pub fn reset_all_submenus(&mut self) {
*self = App::new(self.default_menu.0.clone(), self.default_menu.clone()); *self = App::new(self.default_menu.0.clone(), self.default_menu.clone());
} }
pub fn on_zl(&mut self) { pub fn previous_tab(&mut self) {
if self.page == AppPage::SUBMENU { if self.page == AppPage::SUBMENU {
self.tabs.previous(); self.tabs.previous();
self.set_sub_menu_items(); self.set_sub_menu_items();
} }
} }
pub fn on_zr(&mut self) { pub fn next_tab(&mut self) {
if self.page == AppPage::SUBMENU { if self.page == AppPage::SUBMENU {
self.tabs.next(); self.tabs.next();
self.set_sub_menu_items(); self.set_sub_menu_items();
@ -571,35 +578,40 @@ impl<'a> App<'a> {
serde_json::to_string(&settings).unwrap() serde_json::to_string(&settings).unwrap()
} }
/// Returns the current menu selections and the default menu selections. /// Returns the current menu selections and the default menu selections.
pub fn get_menu_selections(&self) -> String { pub fn get_menu_selections(&self) -> String {
serde_json::to_string( serde_json::to_string(&MenuJsonStruct {
&MenuJsonStruct {
menu: serde_json::from_str(self.to_json().as_str()).unwrap(), menu: serde_json::from_str(self.to_json().as_str()).unwrap(),
defaults_menu: serde_json::from_str(self.default_menu.1.clone().as_str()).unwrap(), defaults_menu: serde_json::from_str(self.default_menu.1.clone().as_str()).unwrap(),
}).unwrap() })
.unwrap()
} }
pub fn submenu_ids(&self) -> Vec<&str> { pub fn submenu_ids(&self) -> Vec<&str> {
return self.menu_items return self
.values() .menu_items
.flat_map(|multi_stateful_list| { .values()
multi_stateful_list .flat_map(|multi_stateful_list| {
.lists multi_stateful_list
.iter() .lists
.flat_map(|sub_stateful_list| { .iter()
sub_stateful_list .flat_map(|sub_stateful_list| {
.items sub_stateful_list
.iter() .items
.map(|submenu| submenu.submenu_id) .iter()
}) .map(|submenu| submenu.submenu_id)
}) })
.collect::<Vec<&str>>(); })
.collect::<Vec<&str>>();
} }
} }
fn render_submenu_page<B: Backend>(f: &mut Frame<B>, app: &mut App, list_chunks: Vec<Rect>, help_chunk: Rect) { fn render_submenu_page<B: Backend>(
f: &mut Frame<B>,
app: &mut App,
list_chunks: Vec<Rect>,
help_chunk: Rect,
) {
let tab_selected = app.tab_selected(); let tab_selected = app.tab_selected();
let mut item_help = None; let mut item_help = None;
for (list_section, stateful_list) in app for (list_section, stateful_list) in app
@ -648,11 +660,16 @@ fn render_submenu_page<B: Backend>(f: &mut Frame<B>, app: &mut App, list_chunks:
item_help.unwrap_or("").replace('\"', "") item_help.unwrap_or("").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));
f.render_widget(help_paragraph, help_chunk); f.render_widget(help_paragraph, help_chunk);
} }
pub fn render_toggle_page<B: Backend>(f: &mut Frame<B>, app: &mut App, list_chunks: Vec<Rect>, help_chunk: Rect) { pub fn render_toggle_page<B: Backend>(
f: &mut Frame<B>,
app: &mut App,
list_chunks: Vec<Rect>,
help_chunk: Rect,
) {
let (title, help_text, mut sub_menu_str_lists) = app.sub_menu_strs_and_states(); let (title, help_text, mut sub_menu_str_lists) = app.sub_menu_strs_and_states();
for list_section in 0..sub_menu_str_lists.len() { for list_section in 0..sub_menu_str_lists.len() {
let sub_menu_str = sub_menu_str_lists[list_section].0.clone(); let sub_menu_str = sub_menu_str_lists[list_section].0.clone();
@ -677,15 +694,17 @@ pub fn render_toggle_page<B: Backend>(f: &mut Frame<B>, app: &mut App, list_chun
.highlight_symbol(">> "); .highlight_symbol(">> ");
f.render_stateful_widget(values_list, list_chunks[list_section], sub_menu_state); f.render_stateful_widget(values_list, list_chunks[list_section], sub_menu_state);
} }
let help_paragraph = Paragraph::new( let help_paragraph = Paragraph::new(help_text.replace('\"', "") + "\nL: Reset Current Menu")
help_text.replace('\"', "") + "\nL: Reset Current Menu",
)
.style(Style::default().fg(Color::Cyan)); .style(Style::default().fg(Color::Cyan));
f.render_widget(help_paragraph, help_chunk); f.render_widget(help_paragraph, help_chunk);
} }
pub fn render_slider_page<B: Backend>(
pub fn render_slider_page<B: Backend>(f: &mut Frame<B>, app: &mut App, vertical_chunk: Rect, help_chunk: Rect) { f: &mut Frame<B>,
app: &mut App,
vertical_chunk: Rect,
help_chunk: Rect,
) {
let (_title, help_text, gauge_vals) = app.sub_menu_strs_for_slider(); let (_title, help_text, gauge_vals) = app.sub_menu_strs_for_slider();
let abs_min = gauge_vals.abs_min; let abs_min = gauge_vals.abs_min;
let abs_max = gauge_vals.abs_max; let abs_max = gauge_vals.abs_max;
@ -693,9 +712,18 @@ pub fn render_slider_page<B: Backend>(f: &mut Frame<B>, app: &mut App, vertical_
let selected_max = gauge_vals.selected_max; let selected_max = gauge_vals.selected_max;
let lbl_ratio = 0.95; // Needed so that the upper limit label is visible let lbl_ratio = 0.95; // Needed so that the upper limit label is visible
let constraints = [ let constraints = [
Constraint::Ratio((lbl_ratio * (selected_min-abs_min) as f32) as u32, abs_max-abs_min), Constraint::Ratio(
Constraint::Ratio((lbl_ratio * (selected_max-selected_min) as f32) as u32, abs_max-abs_min), (lbl_ratio * (selected_min - abs_min) as f32) as u32,
Constraint::Ratio((lbl_ratio * (abs_max-selected_max) as f32) as u32, abs_max-abs_min), abs_max - abs_min,
),
Constraint::Ratio(
(lbl_ratio * (selected_max - selected_min) as f32) as u32,
abs_max - abs_min,
),
Constraint::Ratio(
(lbl_ratio * (abs_max - selected_max) as f32) as u32,
abs_max - abs_min,
),
Constraint::Min(3), // For upper limit label Constraint::Min(3), // For upper limit label
]; ];
let gauge_chunks = Layout::default() let gauge_chunks = Layout::default()
@ -703,12 +731,7 @@ pub fn render_slider_page<B: Backend>(f: &mut Frame<B>, app: &mut App, vertical_
.constraints(constraints) .constraints(constraints)
.split(vertical_chunk); .split(vertical_chunk);
let slider_lbls = [ let slider_lbls = [abs_min, selected_min, selected_max, abs_max];
abs_min,
selected_min,
selected_max,
abs_max,
];
for (idx, lbl) in slider_lbls.iter().enumerate() { for (idx, lbl) in slider_lbls.iter().enumerate() {
let mut line_set = tui::symbols::line::NORMAL; let mut line_set = tui::symbols::line::NORMAL;
line_set.horizontal = "-"; line_set.horizontal = "-";
@ -721,24 +744,16 @@ pub fn render_slider_page<B: Backend>(f: &mut Frame<B>, app: &mut App, vertical_
if idx == 1 { if idx == 1 {
// Slider between selected_min and selected_max // Slider between selected_min and selected_max
match gauge_vals.state { match gauge_vals.state {
GaugeState::MinHover => { GaugeState::MinHover => gauge = gauge.style(Style::default().fg(Color::Red)),
gauge = gauge.style(Style::default().fg(Color::Red)) GaugeState::MinSelected => gauge = gauge.style(Style::default().fg(Color::Green)),
}
GaugeState::MinSelected => {
gauge = gauge.style(Style::default().fg(Color::Green))
}
_ => {} _ => {}
} }
gauge = gauge.gauge_style(Style::default().fg(Color::Yellow).bg(Color::Black)); gauge = gauge.gauge_style(Style::default().fg(Color::Yellow).bg(Color::Black));
} else if idx == 2 { } else if idx == 2 {
// Slider between selected_max and abs_max // Slider between selected_max and abs_max
match gauge_vals.state { match gauge_vals.state {
GaugeState::MaxHover => { GaugeState::MaxHover => gauge = gauge.style(Style::default().fg(Color::Red)),
gauge = gauge.style(Style::default().fg(Color::Red)) GaugeState::MaxSelected => gauge = gauge.style(Style::default().fg(Color::Green)),
}
GaugeState::MaxSelected => {
gauge = gauge.style(Style::default().fg(Color::Green))
}
_ => {} _ => {}
} }
} else if idx == 3 { } else if idx == 3 {
@ -762,9 +777,7 @@ pub fn render_slider_page<B: Backend>(f: &mut Frame<B>, app: &mut App, vertical_
f.render_widget(gauge, gauge_chunks[idx]); f.render_widget(gauge, gauge_chunks[idx]);
} }
let help_paragraph = Paragraph::new( let help_paragraph = Paragraph::new(help_text.replace('\"', "") + "\nL: Reset Current Menu")
help_text.replace('\"', "") + "\nL: Reset Current Menu",
)
.style(Style::default().fg(Color::Cyan)); .style(Style::default().fg(Color::Cyan));
f.render_widget(help_paragraph, help_chunk); f.render_widget(help_paragraph, help_chunk);
} }
@ -846,22 +859,20 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
// is not publicly exposed, and the attribute defaults to true. // is not publicly exposed, and the attribute defaults to true.
// https://github.com/fdehau/tui-rs/blob/v0.19.0/src/layout.rs#L121 // https://github.com/fdehau/tui-rs/blob/v0.19.0/src/layout.rs#L121
let vertical_chunks: Vec<Rect> = vertical_chunks let vertical_chunks: Vec<Rect> = vertical_chunks
.iter() .iter()
.map(|chunk| { .map(|chunk| {
Layout::default() Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints( .constraints(
[ [
Constraint::Length(NX_TUI_WIDTH), // Width of the TUI terminal Constraint::Length(NX_TUI_WIDTH), // Width of the TUI terminal
Constraint::Min(0), // Fill the remainder margin Constraint::Min(0), // Fill the remainder margin
] ]
.as_ref(), .as_ref(),
) )
.split(*chunk)[0] .split(*chunk)[0]
} })
) .collect();
.collect();
let list_chunks = Layout::default() let list_chunks = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
@ -881,6 +892,6 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
AppPage::SUBMENU => render_submenu_page(f, app, list_chunks, vertical_chunks[2]), AppPage::SUBMENU => render_submenu_page(f, app, list_chunks, vertical_chunks[2]),
AppPage::SLIDER => render_slider_page(f, app, vertical_chunks[1], vertical_chunks[2]), AppPage::SLIDER => render_slider_page(f, app, vertical_chunks[1], vertical_chunks[2]),
AppPage::TOGGLE => render_toggle_page(f, app, list_chunks, vertical_chunks[2]), AppPage::TOGGLE => render_toggle_page(f, app, list_chunks, vertical_chunks[2]),
AppPage::CONFIRMATION => todo!() AppPage::CONFIRMATION => todo!(),
} }
} }

View file

@ -5,21 +5,28 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
}; };
#[cfg(feature = "has_terminal")] use std::error::Error;
use tui::backend::CrosstermBackend;
#[cfg(feature = "has_terminal")] #[cfg(feature = "has_terminal")]
use std::{ use std::{
io, io,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use std::error::Error; #[cfg(feature = "has_terminal")]
use tui::backend::CrosstermBackend;
use tui::Terminal; use tui::Terminal;
use training_mod_consts::*; use training_mod_consts::*;
fn test_backend_setup<'a>(ui_menu: UiMenu<'a>, menu_defaults: (UiMenu<'a>, String)) -> Result< fn test_backend_setup<'a>(
(Terminal<training_mod_tui::TestBackend>, training_mod_tui::App<'a>), ui_menu: UiMenu<'a>,
Box<dyn Error>> { menu_defaults: (UiMenu<'a>, String),
) -> Result<
(
Terminal<training_mod_tui::TestBackend>,
training_mod_tui::App<'a>,
),
Box<dyn Error>,
> {
let app = training_mod_tui::App::<'a>::new(ui_menu, menu_defaults); let app = training_mod_tui::App::<'a>::new(ui_menu, menu_defaults);
let backend = tui::backend::TestBackend::new(75, 15); let backend = tui::backend::TestBackend::new(75, 15);
let terminal = Terminal::new(backend)?; let terminal = Terminal::new(backend)?;
@ -103,7 +110,7 @@ fn test_save_and_reset_defaults() -> Result<(), Box<dyn Error>> {
// Return to submenu selection // Return to submenu selection
app.on_b(); app.on_b();
// Save Defaults // Save Defaults
app.on_x(); app.save_defaults();
// Enter Mash Toggles again // Enter Mash Toggles again
app.on_a(); app.on_a();
// Unset Mash Airdodge // Unset Mash Airdodge
@ -127,7 +134,7 @@ fn test_save_and_reset_defaults() -> Result<(), Box<dyn Error>> {
); );
// Reset current menu alone to defaults // Reset current menu alone to defaults
app.on_l(); app.reset_current_submenu();
let menu_json = app.get_menu_selections(); let menu_json = app.get_menu_selections();
let menu_struct = serde_json::from_str::<MenuJsonStruct>(&menu_json).unwrap(); let menu_struct = serde_json::from_str::<MenuJsonStruct>(&menu_json).unwrap();
let menu = menu_struct.menu; let menu = menu_struct.menu;
@ -155,7 +162,7 @@ fn test_save_and_reset_defaults() -> Result<(), Box<dyn Error>> {
// Return to submenu selection // Return to submenu selection
app.on_b(); app.on_b();
// Save defaults // Save defaults
app.on_x(); app.save_defaults();
// Go back in and unset Jump // Go back in and unset Jump
app.on_a(); app.on_a();
app.on_down(); app.on_down();
@ -163,7 +170,7 @@ fn test_save_and_reset_defaults() -> Result<(), Box<dyn Error>> {
// Return to submenu selection // Return to submenu selection
app.on_b(); app.on_b();
// Reset all to defaults // Reset all to defaults
app.on_r(); app.reset_all_submenus();
let menu_json = app.get_menu_selections(); let menu_json = app.get_menu_selections();
let menu_struct = serde_json::from_str::<MenuJsonStruct>(&menu_json).unwrap(); let menu_struct = serde_json::from_str::<MenuJsonStruct>(&menu_json).unwrap();
let menu = menu_struct.menu; let menu = menu_struct.menu;
@ -177,7 +184,6 @@ fn test_save_and_reset_defaults() -> Result<(), Box<dyn Error>> {
"The menu should have Mash Airdodge off and Followup Jump on" "The menu should have Mash Airdodge off and Followup Jump on"
); );
Ok(()) Ok(())
} }
@ -192,16 +198,19 @@ fn main() -> Result<(), Box<dyn Error>> {
menu_defaults = (ui_menu(MENU), serde_json::to_string(&MENU).unwrap()); menu_defaults = (ui_menu(MENU), serde_json::to_string(&MENU).unwrap());
} }
#[cfg(not(feature = "has_terminal"))] { #[cfg(not(feature = "has_terminal"))]
{
let (mut terminal, mut app) = test_backend_setup(menu, menu_defaults)?; let (mut terminal, mut app) = test_backend_setup(menu, menu_defaults)?;
if inputs.is_some() { if inputs.is_some() {
inputs.unwrap().split(",").for_each(|input| { inputs
match input.to_uppercase().as_str() { .unwrap()
"X" => app.on_x(), .split(",")
"L" => app.on_l(), .for_each(|input| match input.to_uppercase().as_str() {
"R" => app.on_r(), "X" => app.save_defaults(),
"O" => app.on_zl(), "Y" => app.reset_current_submenu(),
"P" => app.on_zr(), "Z" => app.reset_all_submenus(),
"L" => app.previous_tab(),
"R" => app.next_tab(),
"A" => app.on_a(), "A" => app.on_a(),
"B" => app.on_b(), "B" => app.on_b(),
"UP" => app.on_up(), "UP" => app.on_up(),
@ -209,8 +218,7 @@ fn main() -> Result<(), Box<dyn Error>> {
"LEFT" => app.on_left(), "LEFT" => app.on_left(),
"RIGHT" => app.on_right(), "RIGHT" => app.on_right(),
_ => {} _ => {}
} })
})
} }
let frame_res = terminal.draw(|f| training_mod_tui::ui(f, &mut app))?; let frame_res = terminal.draw(|f| training_mod_tui::ui(f, &mut app))?;
let menu_json = app.get_menu_selections(); let menu_json = app.get_menu_selections();
@ -226,7 +234,8 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("Menu:\n{menu_json}"); println!("Menu:\n{menu_json}");
} }
#[cfg(feature = "has_terminal")] { #[cfg(feature = "has_terminal")]
{
let app = training_mod_tui::App::new(menu, menu_defaults); let app = training_mod_tui::App::new(menu, menu_defaults);
// setup terminal // setup terminal
@ -281,11 +290,11 @@ fn run_app<B: tui::backend::Backend>(
if let Event::Key(key) = event::read()? { if let Event::Key(key) = event::read()? {
match key.code { match key.code {
KeyCode::Char('q') => return Ok(menu_json), KeyCode::Char('q') => return Ok(menu_json),
KeyCode::Char('x') => app.on_x(), KeyCode::Char('x') => app.save_defaults(),
KeyCode::Char('p') => app.on_zr(), KeyCode::Char('p') => app.reset_current_submenu(),
KeyCode::Char('o') => app.on_zl(), KeyCode::Char('o') => app.reset_all_submenus(),
KeyCode::Char('r') => app.on_r(), KeyCode::Char('r') => app.next_tab(),
KeyCode::Char('l') => app.on_l(), KeyCode::Char('l') => app.previous_tab(),
KeyCode::Left => app.on_left(), KeyCode::Left => app.on_left(),
KeyCode::Right => app.on_right(), KeyCode::Right => app.on_right(),
KeyCode::Down => app.on_down(), KeyCode::Down => app.on_down(),