diff --git a/Cargo.toml b/Cargo.toml index 54d443d0..86665e7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,10 @@ skyline_smash = { git = "https://github.com/ultimate-research/skyline-smash.git" skyline-web = { git = "https://github.com/skyline-rs/skyline-web.git" } bitflags = "1.2.1" parking_lot = { version = "0.12.0", features = ["nightly"] } +include-flate = "0.1.4" lazy_static = "1.4.0" owo-colors = "2.1.0" once_cell = "1.12.0" -ramhorns = "0.12.0" paste = "1.0" num = "0.4.0" num-derive = "0.3" @@ -25,6 +25,7 @@ wsl = "0.1.0" strum = "0.21.0" strum_macros = "0.21.0" minreq = { version = "2", features = ["https-native", "json-using-serde"] } +sarc = { version = "1.2.0", features = [], default_features = false } serde = { version = "1", features = ["derive"] } serde_json = "1" toml = "0.5.9" @@ -54,4 +55,4 @@ plugin-dependencies = [ [features] outside_training_mode = [] -web_session_single_thread = [] +layout_arc_from_file = [] diff --git a/ryujinx_build.ps1 b/ryujinx_build.ps1 index c65fa26e..24498c31 100644 --- a/ryujinx_build.ps1 +++ b/ryujinx_build.ps1 @@ -1,4 +1,4 @@ $IP=(Test-Connection -ComputerName (hostname) -Count 1 | Select -ExpandProperty IPV4Address).IPAddressToString -cargo skyline build --release +cargo skyline build --release --features layout_arc_from_file Copy-Item target/aarch64-skyline-switch/release/libtraining_modpack.nro 'C:\Users\Jdsam\AppData\Roaming\Ryujinx\mods\contents\01006A800016E000\romfs\skyline\plugins\' cargo skyline listen --ip=$IP \ No newline at end of file diff --git a/ryujinx_build.sh b/ryujinx_build.sh index af46fcb8..e6621dd9 100644 --- a/ryujinx_build.sh +++ b/ryujinx_build.sh @@ -6,7 +6,7 @@ SMASH_APPLICATION_PATH="C:\Users\Jdsam\Downloads\Super Smash Bros. Ultimate (Wor RYUJINX_SMASH_SKYLINE_PLUGINS_PATH="/mnt/c/Users/Jdsam/AppData/Roaming/Ryujinx/mods/contents/01006a800016e000/romfs/skyline/plugins" # Build with release feature -cargo skyline build --release +cargo skyline build --release --features layout_arc_from_file # Copy over to plugins path cp target/aarch64-skyline-switch/release/libtraining_modpack.nro $RYUJINX_SMASH_SKYLINE_PLUGINS_PATH diff --git a/src/static/layout.arc b/src/static/layout.arc new file mode 100644 index 00000000..9ed5845e Binary files /dev/null and b/src/static/layout.arc differ diff --git a/src/training/ui/display.rs b/src/training/ui/display.rs index 4650dcc8..de8055d4 100644 --- a/src/training/ui/display.rs +++ b/src/training/ui/display.rs @@ -2,29 +2,21 @@ use crate::training::ui; use skyline::nn::ui2d::*; use smash::ui2d::{SmashPane, SmashTextBox}; -pub static NUM_DISPLAY_PANES: usize = 1; - macro_rules! display_parent_fmt { ($x:ident) => { - format!("trMod_disp_{}", $x).as_str() - }; -} - -macro_rules! display_pic_fmt { - ($x:ident) => { - format!("trMod_disp_{}_base", $x).as_str() + format!("TrModDisp{}", $x).as_str() }; } macro_rules! display_header_fmt { ($x:ident) => { - format!("trMod_disp_{}_header", $x).as_str() + format!("TrModDisp{}Header", $x).as_str() }; } macro_rules! display_txt_fmt { ($x:ident) => { - format!("trMod_disp_{}_txt", $x).as_str() + format!("TrModDisp{}Txt", $x).as_str() }; } @@ -57,91 +49,7 @@ pub unsafe fn draw(root_pane: &mut Pane) { if let Some(text) = root_pane.find_pane_by_name_recursive(display_txt_fmt!(notification_idx)) { let text = text.as_textbox(); text.set_text_string(message); + text.set_default_material_colors(); text.set_color(color.r, color.g, color.b, color.a); } -} - - -pub static BUILD_PIC_BASE: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - (0..NUM_DISPLAY_PANES).for_each(|idx| { - let block = block as *mut ResPictureWithTex<1>; - let mut pic_block = *block; - pic_block.set_name(display_pic_fmt!(idx)); - pic_block.set_pos(ResVec3::default()); - let pic_pane = build!(pic_block, ResPictureWithTex<1>, kind, Picture); - pic_pane.detach(); - - // pic is loaded first, we can create our parent pane here. - let disp_pane_kind = u32::from_le_bytes([b'p', b'a', b'n', b'1']); - let mut disp_pane_block = ResPane::new(display_parent_fmt!(idx)); - disp_pane_block.set_pos(ResVec3::new(806.0, -50.0 - (idx as f32 * 110.0), 0.0)); - let disp_pane = build!(disp_pane_block, ResPane, disp_pane_kind, Pane); - disp_pane.detach(); - root_pane.append_child(disp_pane); - disp_pane.append_child(pic_pane); - }); -}; - -pub static BUILD_PANE_TXT: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - (0..NUM_DISPLAY_PANES).for_each(|idx| { - let disp_pane = root_pane - .find_pane_by_name(display_parent_fmt!(idx), true) - .unwrap(); - - let block = block as *mut ResTextBox; - let mut text_block = *block; - text_block.set_name(display_txt_fmt!(idx)); - text_block.set_pos(ResVec3::new(-10.0, -25.0, 0.0)); - let text_pane = build!(text_block, ResTextBox, kind, TextBox); - text_pane.set_text_string(format!("Pane {idx}!").as_str()); - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - text_pane.set_default_material_colors(); - text_pane.detach(); - disp_pane.append_child(text_pane); - }); -}; - -pub static BUILD_HEADER_TXT: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - (0..NUM_DISPLAY_PANES).for_each(|idx| { - let disp_pane = root_pane - .find_pane_by_name(display_parent_fmt!(idx), true) - .unwrap(); - - let block = block as *mut ResTextBox; - let mut header_block = *block; - header_block.set_name(display_header_fmt!(idx)); - header_block.set_pos(ResVec3::new(0.0, 25.0, 0.0)); - let header_pane = build!(header_block, ResTextBox, kind, TextBox); - header_pane.set_text_string(format!("Header {idx}").as_str()); - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - header_pane.set_default_material_colors(); - // Header should be white text - header_pane.set_color(255, 255, 255, 255); - header_pane.detach(); - disp_pane.append_child(header_pane); - }); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/training/ui/menu.rs b/src/training/ui/menu.rs index e962a53d..4ad094d5 100644 --- a/src/training/ui/menu.rs +++ b/src/training/ui/menu.rs @@ -3,21 +3,9 @@ use skyline::nn::ui2d::*; use smash::ui2d::{SmashPane, SmashTextBox}; use training_mod_tui::{App, AppPage}; use training_mod_tui::gauge::GaugeState; -use crate::training::ui; - -pub static NUM_MENU_TEXT_OPTIONS: usize = 27; -pub static NUM_MENU_TEXT_SLIDERS: usize = 2; -pub static NUM_MENU_TABS: usize = 3; - -pub static mut HAS_SORTED_MENU_CHILDREN: bool = false; - -pub static mut MENU_PANE_PTR: u64 = 0; -const MENU_POS : ResVec3 = ResVec3 { - x: -360.0, - y: 440.0, - z: 0.0 -}; +pub static NUM_MENU_TEXT_OPTIONS: usize = 33; +pub static _NUM_MENU_TABS: usize = 3; const BG_LEFT_ON_WHITE_COLOR: ResColor = ResColor { r: 0, @@ -61,150 +49,6 @@ const BG_LEFT_SELECTED_WHITE_COLOR: ResColor = ResColor { a: 255, }; -const BLACK: ResColor = ResColor { - r: 0, - g: 0, - b: 0, - a: 255, -}; - -pub static mut MENU_NAME : &str = "trMod_menu"; -pub static mut SLIDER_MENU_NAME : &str = "slider_menu"; -pub static mut SLIDER_TITLE_NAME : &str = "slider_title"; -pub static mut SLIDER_UI_CONTAINER_NAME : &str = "slider_ui_container"; - -macro_rules! menu_text_name_fmt { - ($x:ident, $y:ident) => { - format!("trMod_menu_opt_{}_{}", $x, $y).as_str() - }; -} - -macro_rules! menu_text_check_fmt { - ($x:ident, $y:ident) => { - format!("trMod_menu_check_{}_{}", $x, $y).as_str() - }; -} - -macro_rules! menu_text_bg_left_fmt { - ($x:ident, $y:ident) => { - format!("trMod_menu_bg_left_{}_{}", $x, $y).as_str() - }; -} - -macro_rules! menu_text_bg_back_fmt { - ($x:ident, $y:ident) => { - format!("trMod_menu_bg_back_{}_{}", $x, $y).as_str() - }; -} - -macro_rules! menu_tab_fmt { - ($x:ident) => { - format!("trMod_menu_tab_{}", $x).as_str() - }; -} - -macro_rules! menu_tab_help_fmt { - ($x:ident) => { - format!("trMod_menu_tab_help_{}", $x).as_str() - }; -} - -macro_rules! defaults_help_text { - ($x:ident) => { - format!("defaults_help_txt_{}", $x).as_str() - }; - ($x:literal) => { - format!("defaults_help_txt_{}", $x).as_str() - }; -} - -macro_rules! defaults_help_button { - ($x:ident) => { - format!("defaults_help_btn_{}", $x).as_str() - }; - ($x:literal) => { - format!("defaults_help_txt_{}", $x).as_str() - }; -} - -macro_rules! menu_text_slider_fmt { - ($x:ident) => { - format!("trMod_menu_slider_{}", $x).as_str() - }; -} - -macro_rules! menu_slider_label_fmt { - ($x:ident) => { - format!("trMod_menu_slider_{}_lbl", $x).as_str() - }; -} - -macro_rules! menu_slider_button_label_fmt { - ($x:ident) => { - format!("slider_item_btn_{}_lbl", $x).as_str() - }; -} - -macro_rules! menu_slider_button_fg_fmt { - ($x:ident) => { - format!("slider_btn_fg_{}_lbl", $x).as_str() - }; -} - -// Sort all panes in under menu pane such that text and check options -// are last -pub unsafe fn all_menu_panes_sorted(root_pane: &Pane) -> Vec<&mut Pane> { - let mut panes = (0..NUM_MENU_TEXT_OPTIONS) - .flat_map(|idx| { - let x = idx % 3; - let y = idx / 3; - [ - root_pane - .find_pane_by_name_recursive(menu_text_name_fmt!(x, y)) - .unwrap(), - root_pane - .find_pane_by_name_recursive(menu_text_check_fmt!(x, y)) - .unwrap(), - root_pane - .find_pane_by_name_recursive(menu_text_bg_left_fmt!(x, y)) - .unwrap(), - root_pane - .find_pane_by_name_recursive(menu_text_bg_back_fmt!(x, y)) - .unwrap(), - ] - }) - .collect::>(); - - panes.append( - &mut (0..NUM_MENU_TEXT_SLIDERS) - .map(|idx| { - root_pane - .find_pane_by_name_recursive(menu_text_slider_fmt!(idx)) - .unwrap() - }) - .collect::>(), - ); - - panes.append( - &mut (0..NUM_MENU_TEXT_SLIDERS) - .map(|idx| { - root_pane - .find_pane_by_name_recursive(menu_slider_label_fmt!(idx)) - .unwrap() - }) - .collect::>(), - ); - - panes.sort_by(|a, _| { - if a.get_name().contains("opt") || a.get_name().contains("check") { - std::cmp::Ordering::Greater - } else { - std::cmp::Ordering::Less - } - }); - - panes -} unsafe fn render_submenu_page(app: &App, root_pane: &mut Pane) { let tab_selected = app.tab_selected(); @@ -213,59 +57,40 @@ unsafe fn render_submenu_page(app: &App, root_pane: &mut Pane) { (0..NUM_MENU_TEXT_OPTIONS) // Valid options in this submenu .filter_map(|idx| tab.idx_to_list_idx_opt(idx)) - .map(|(list_section, list_idx)| { - ( - list_section, - list_idx, - root_pane - .find_pane_by_name_recursive(menu_text_name_fmt!( - list_section, - list_idx - )) - .unwrap(), - root_pane - .find_pane_by_name_recursive(menu_text_bg_left_fmt!( - list_section, - list_idx - )) - .unwrap(), - root_pane - .find_pane_by_name_recursive(menu_text_bg_back_fmt!( - list_section, - list_idx - )) - .unwrap(), - ) - }) - .for_each(|(list_section, list_idx, text, bg_left, bg_back)| { + .for_each(|(list_section, list_idx)| { + let menu_button_row = root_pane.find_pane_by_name_recursive( + format!("TrModMenuButtonRow{list_idx}").as_str() + ).unwrap(); + menu_button_row.set_visible(true); + let menu_button = menu_button_row.find_pane_by_name_recursive( + format!("Button{list_section}").as_str() + ).unwrap(); + menu_button.set_visible(true); + let title_text = menu_button.find_pane_by_name_recursive("TitleTxt") + .unwrap().as_textbox(); + let title_bg = menu_button.find_pane_by_name_recursive("TitleBg") + .unwrap().as_picture(); + let list = &tab.lists[list_section]; let submenu = &list.items[list_idx]; let is_selected = list.state.selected().filter(|s| *s == list_idx).is_some(); - let text = text.as_textbox(); - text.set_text_string(submenu.submenu_title); - text.set_visible(true); - let bg_left_material = &mut *bg_left.as_picture().material; + title_text.set_text_string(submenu.submenu_title); + let title_bg_material = &mut *title_bg.material; if is_selected { - if let Some(footer) = - root_pane.find_pane_by_name_recursive("trMod_menu_footer_txt") - { - footer.as_textbox().set_text_string(submenu.help_text); - } - bg_left_material.set_white_res_color(BG_LEFT_ON_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_ON_BLACK_COLOR); - text.text_shadow_enable(true); - text.text_outline_enable(true); - text.set_color(255, 255, 255, 255); + root_pane.find_pane_by_name_recursive("FooterTxt") + .unwrap().as_textbox().set_text_string(submenu.help_text); + 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_text.text_shadow_enable(true); + title_text.text_outline_enable(true); + title_text.set_color(255, 255, 255, 255); } else { - bg_left_material.set_white_res_color(BG_LEFT_OFF_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_OFF_BLACK_COLOR); - text.text_shadow_enable(false); - text.text_outline_enable(false); - text.set_color(85, 89, 92, 255); + title_bg_material.set_white_res_color(BG_LEFT_OFF_WHITE_COLOR); + title_bg_material.set_black_res_color(BG_LEFT_OFF_BLACK_COLOR); + title_text.text_shadow_enable(false); + title_text.text_outline_enable(false); + title_text.set_color(178, 199, 211, 255); } - - bg_left.set_visible(true); - bg_back.set_visible(true); }); } @@ -277,56 +102,47 @@ unsafe fn render_toggle_page(app: &App, root_pane: &mut Pane) { sub_menu_str .iter() .enumerate() - .for_each(|(idx, (checked, name))| { - let is_selected = sub_menu_state.selected().filter(|s| *s == idx).is_some(); - if let Some(text) = root_pane - .find_pane_by_name_recursive(menu_text_name_fmt!(list_section, idx)) - { - let text = text.as_textbox(); - text.set_text_string(name); + .for_each(|(list_idx, (checked, name))| { + let menu_button_row = root_pane.find_pane_by_name_recursive( + format!("TrModMenuButtonRow{list_idx}").as_str() + ).unwrap(); + menu_button_row.set_visible(true); + let menu_button = menu_button_row.find_pane_by_name_recursive( + format!("Button{list_section}").as_str() + ).unwrap(); + menu_button.set_visible(true); - if is_selected { - text.text_shadow_enable(true); - text.text_outline_enable(true); - text.set_color(255, 255, 255, 255); - } else { - text.text_shadow_enable(false); - text.text_outline_enable(false); - text.set_color(85, 89, 92, 255); - } + let title_text = menu_button.find_pane_by_name_recursive("TitleTxt") + .unwrap().as_textbox(); + let title_bg = menu_button.find_pane_by_name_recursive("TitleBg") + .unwrap().as_picture(); + let value_text = menu_button.find_pane_by_name_recursive("ValueTxt") + .unwrap().as_textbox(); - text.set_visible(true); + let is_selected = sub_menu_state.selected().filter(|s| *s == list_idx).is_some(); + title_text.set_text_string(name); + if is_selected { + title_text.text_shadow_enable(true); + title_text.text_outline_enable(true); + title_text.set_color(255, 255, 255, 255); + } else { + title_text.text_shadow_enable(false); + title_text.text_outline_enable(false); + title_text.set_color(178, 199, 211, 255); } - if let Some(bg_left) = root_pane - .find_pane_by_name_recursive(menu_text_bg_left_fmt!(list_section, idx)) - { - let bg_left_material = &mut *bg_left.as_picture().material; - if is_selected { - bg_left_material.set_white_res_color(BG_LEFT_ON_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_ON_BLACK_COLOR); - } else { - bg_left_material.set_white_res_color(BG_LEFT_OFF_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_OFF_BLACK_COLOR); - } - bg_left.set_visible(true); + let title_bg_material = &mut *title_bg.material; + if is_selected { + title_bg_material.set_white_res_color(BG_LEFT_ON_WHITE_COLOR); + title_bg_material.set_black_res_color(BG_LEFT_ON_BLACK_COLOR); + } else { + title_bg_material.set_white_res_color(BG_LEFT_OFF_WHITE_COLOR); + title_bg_material.set_black_res_color(BG_LEFT_OFF_BLACK_COLOR); } - if let Some(bg_back) = root_pane - .find_pane_by_name_recursive(menu_text_bg_back_fmt!(list_section, idx)) - { - bg_back.set_visible(true); - } - - if let Some(check) = root_pane - .find_pane_by_name_recursive(menu_text_check_fmt!(list_section, idx)) - { - if *checked { - let check = check.as_textbox(); - - check.set_text_string("+"); - check.set_visible(true); - } + if *checked { + value_text.set_text_string("X"); + value_text.set_visible(true); } }); }); @@ -337,111 +153,81 @@ unsafe fn render_slider_page(app: &App, root_pane: &mut Pane) { let selected_min = gauge_vals.selected_min; let selected_max = gauge_vals.selected_max; - if let Some(pane) = root_pane.find_pane_by_name_recursive(SLIDER_MENU_NAME) { - pane.set_visible(true); + let slider_pane = root_pane.find_pane_by_name_recursive("TrModSlider").unwrap(); + slider_pane.set_visible(true); + + let _background = slider_pane.find_pane_by_name_recursive("Background") + .unwrap().as_picture(); + let header = slider_pane.find_pane_by_name_recursive("Header") + .unwrap().as_textbox(); + header.set_text_string(title); + let min_button = slider_pane.find_pane_by_name_recursive("MinButton") + .unwrap().as_picture(); + let max_button = slider_pane.find_pane_by_name_recursive("MaxButton") + .unwrap().as_picture(); + let min_title_text = min_button.find_pane_by_name_recursive("TitleTxt") + .unwrap().as_textbox(); + let min_title_bg = min_button.find_pane_by_name_recursive("TitleBg") + .unwrap().as_picture(); + let min_value_text = min_button.find_pane_by_name_recursive("ValueTxt") + .unwrap().as_textbox(); + let max_title_text = max_button.find_pane_by_name_recursive("TitleTxt") + .unwrap().as_textbox(); + let max_title_bg = max_button.find_pane_by_name_recursive("TitleBg") + .unwrap().as_picture(); + let max_value_text = max_button.find_pane_by_name_recursive("ValueTxt") + .unwrap().as_textbox(); + + min_title_text.set_text_string("Min"); + match gauge_vals.state { + GaugeState::MinHover | GaugeState::MinSelected => { + min_title_text.text_shadow_enable(true); + min_title_text.text_outline_enable(true); + min_title_text.set_color(255, 255, 255, 255); + } + _ => { + min_title_text.text_shadow_enable(false); + min_title_text.text_outline_enable(false); + min_title_text.set_color(178, 199, 211, 255); + } } - if let Some(text) = root_pane.find_pane_by_name_recursive(SLIDER_TITLE_NAME) { - let text = text.as_textbox(); - text.set_text_string(title); + max_title_text.set_text_string("Max"); + match gauge_vals.state { + GaugeState::MaxHover | GaugeState::MaxSelected => { + max_title_text.text_shadow_enable(true); + max_title_text.text_outline_enable(true); + max_title_text.set_color(255, 255, 255, 255); + } + _ => { + max_title_text.text_shadow_enable(false); + max_title_text.text_outline_enable(false); + max_title_text.set_color(178, 199, 211, 255); + } } - (0..NUM_MENU_TEXT_SLIDERS).for_each(|index| { - if let Some(text_pane) = root_pane.find_pane_by_name_recursive( - menu_slider_label_fmt!(index), - ) { - let text_pane = text_pane.as_textbox(); - text_pane.set_visible(true); + min_value_text.set_text_string(&format!("{selected_min}")); + max_value_text.set_text_string(&format!("{selected_max}")); - match index { - 0 => { - text_pane.set_text_string("Min"); + let min_title_bg_material = &mut *min_title_bg.as_picture().material; + let min_colors = match gauge_vals.state { + GaugeState::MinHover => (BG_LEFT_ON_WHITE_COLOR, BG_LEFT_ON_BLACK_COLOR), + GaugeState::MinSelected => (BG_LEFT_SELECTED_WHITE_COLOR, BG_LEFT_SELECTED_BLACK_COLOR), + _ => (BG_LEFT_OFF_WHITE_COLOR, BG_LEFT_OFF_BLACK_COLOR) + }; - match gauge_vals.state { - GaugeState::MinHover | GaugeState::MinSelected => { - text_pane.text_shadow_enable(true); - text_pane.text_outline_enable(true); - text_pane.set_color(255, 255, 255, 255); - } - _ => { - text_pane.text_shadow_enable(false); - text_pane.text_outline_enable(false); - text_pane.set_color(85, 89, 92, 255); - } - } - } - 1 => { - text_pane.set_text_string("Max"); + min_title_bg_material.set_white_res_color(min_colors.0); + min_title_bg_material.set_black_res_color(min_colors.1); - match gauge_vals.state { - GaugeState::MaxHover | GaugeState::MaxSelected => { - text_pane.text_shadow_enable(true); - text_pane.text_outline_enable(true); - text_pane.set_color(255, 255, 255, 255); - } - _ => { - text_pane.text_shadow_enable(false); - text_pane.text_outline_enable(false); - text_pane.set_color(85, 89, 92, 255); - } - } - } - _ => panic!("Unexpected slider label index {}!", index), - } - } + let max_title_bg_material = &mut *max_title_bg.as_picture().material; + let max_colors = match gauge_vals.state { + GaugeState::MaxHover => (BG_LEFT_ON_WHITE_COLOR, BG_LEFT_ON_BLACK_COLOR), + GaugeState::MaxSelected => (BG_LEFT_SELECTED_WHITE_COLOR, BG_LEFT_SELECTED_BLACK_COLOR), + _ => (BG_LEFT_OFF_WHITE_COLOR, BG_LEFT_OFF_BLACK_COLOR) + }; - if let Some(text_pane) = root_pane - .find_pane_by_name_recursive(menu_text_slider_fmt!(index)) - { - let text_pane = text_pane.as_textbox(); - text_pane.set_visible(true); - - match index { - 0 => text_pane.set_text_string(&format!("{selected_min}")), - 1 => text_pane.set_text_string(&format!("{selected_max}")), - _ => panic!("Unexpected slider label index {}!", index), - } - } - - if let Some(bg_left) = root_pane - .find_pane_by_name_recursive(menu_slider_button_fg_fmt!(index)) - { - let bg_left_material = &mut *bg_left.as_picture().material; - - match index { - 0 => match gauge_vals.state { - GaugeState::MinHover => { - bg_left_material.set_white_res_color(BG_LEFT_ON_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_ON_BLACK_COLOR); - } - GaugeState::MinSelected => { - bg_left_material.set_white_res_color(BG_LEFT_SELECTED_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_SELECTED_BLACK_COLOR); - } - _ => { - bg_left_material.set_white_res_color(BG_LEFT_OFF_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_OFF_BLACK_COLOR); - } - }, - 1 => match gauge_vals.state { - GaugeState::MaxHover => { - bg_left_material.set_white_res_color(BG_LEFT_ON_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_ON_BLACK_COLOR); - } - GaugeState::MaxSelected => { - bg_left_material.set_white_res_color(BG_LEFT_SELECTED_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_SELECTED_BLACK_COLOR); - } - _ => { - bg_left_material.set_white_res_color(BG_LEFT_OFF_WHITE_COLOR); - bg_left_material.set_black_res_color(BG_LEFT_OFF_BLACK_COLOR); - } - }, - _ => panic!("Unexpected slider label index {}!", index), - } - bg_left.set_visible(true); - } - }); + max_title_bg_material.set_white_res_color(max_colors.0); + max_title_bg_material.set_black_res_color(max_colors.1); } pub unsafe fn draw(root_pane: &mut Pane) { @@ -469,48 +255,26 @@ pub unsafe fn draw(root_pane: &mut Pane) { } } - let menu_pane = root_pane.find_pane_by_name_recursive(MENU_NAME).unwrap(); - menu_pane.set_visible(QUICK_MENU_ACTIVE); - - if !HAS_SORTED_MENU_CHILDREN { - let sorted_panes = all_menu_panes_sorted(root_pane); - // Place in sorted order such that backings are behind, etc. - sorted_panes.iter().for_each(|p| menu_pane.remove_child(p)); - sorted_panes.iter().for_each(|p| menu_pane.append_child(p)); - - HAS_SORTED_MENU_CHILDREN = true; - } + root_pane.find_pane_by_name_recursive("TrModMenu").unwrap().set_visible(QUICK_MENU_ACTIVE); // Make all invisible first (0..NUM_MENU_TEXT_OPTIONS).for_each(|idx| { - let x = idx % 3; - let y = idx / 3; - root_pane - .find_pane_by_name_recursive(menu_text_name_fmt!(x, y)) - .map(|text| text.set_visible(false)); - root_pane - .find_pane_by_name_recursive(menu_text_check_fmt!(x, y)) - .map(|text| text.set_visible(false)); - root_pane - .find_pane_by_name_recursive(menu_text_bg_left_fmt!(x, y)) - .map(|text| text.set_visible(false)); - root_pane - .find_pane_by_name_recursive(menu_text_bg_back_fmt!(x, y)) - .map(|text| text.set_visible(false)); - }); - (0..NUM_MENU_TEXT_SLIDERS).for_each(|idx| { - root_pane - .find_pane_by_name_recursive(menu_text_slider_fmt!(idx)) - .map(|text| text.set_visible(false)); + let col_idx = idx % 3; + let row_idx = idx / 3; - root_pane - .find_pane_by_name_recursive(menu_slider_label_fmt!(idx)) - .map(|text| text.set_visible(false)); + let menu_button_row = root_pane.find_pane_by_name_recursive( + format!("TrModMenuButtonRow{row_idx}").as_str() + ).unwrap(); + menu_button_row.set_visible(false); + let menu_button = menu_button_row.find_pane_by_name_recursive( + format!("Button{col_idx}").as_str() + ).unwrap(); + menu_button.set_visible(false); + + menu_button.find_pane_by_name_recursive("ValueTxt").unwrap().set_visible(false); }); - root_pane - .find_pane_by_name_recursive(SLIDER_MENU_NAME) - .map(|pane| pane.set_visible(false)); + root_pane.find_pane_by_name_recursive("TrModSlider").unwrap().set_visible(false); let app_tabs = &app.tabs.items; let tab_selected = app.tabs.state.selected().unwrap(); @@ -526,10 +290,60 @@ pub unsafe fn draw(root_pane: &mut Pane) { }; let tab_titles = [prev_tab, tab_selected, next_tab].map(|idx| app_tabs[idx]); - (0..NUM_MENU_TABS).for_each(|idx| { - root_pane - .find_pane_by_name_recursive(format!("trMod_menu_tab_{idx}").as_str()) - .map(|text| text.as_textbox().set_text_string(tab_titles[idx])); + [(Some(0xE0E6), "LeftTab"), (None, "CurrentTab"), (Some(0xE0E7), "RightTab")] + .iter().enumerate().for_each(|(idx, (key, name))| { + let key_help_pane = root_pane.find_pane_by_name_recursive(name) + .unwrap(); + + let icon_pane = key_help_pane.find_pane_by_name_recursive("set_txt_icon") + .unwrap().as_textbox(); + let help_pane = key_help_pane.find_pane_by_name_recursive("set_txt_help") + .unwrap().as_textbox(); + icon_pane.set_text_string(""); + + // Left/Right tabs have keys + if let Some(key) = key { + let it = icon_pane.text_buf as *mut u16; + icon_pane.text_len = 1; + *it = *key as u16; + *(it.add(1)) = 0x0; + } else { + // Center tab should be highlighted + help_pane.set_default_material_colors(); + help_pane.set_color(255, 255, 0, 255); + } + help_pane.set_text_string(tab_titles[idx]); + }); + [(0xE0E2, "SaveDefaults"), (0xE0E4, "ResetCurrentDefaults"), (0xE0E5, "ResetAllDefaults")].iter() + .for_each(|(key, name)| { + let key_help_pane = root_pane.find_pane_by_name_recursive(name) + .unwrap(); + + let icon_pane = key_help_pane.find_pane_by_name_recursive("set_txt_icon") + .unwrap().as_textbox(); + icon_pane.set_text_string(""); + let it = icon_pane.text_buf as *mut u16; + icon_pane.text_len = 1; + *it = *key as u16; + *(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::>() + .join(" "); + key_help_pane.find_pane_by_name_recursive("set_txt_help") + .unwrap().as_textbox().set_text_string(title_case.as_str()); }); match app.page { @@ -538,552 +352,4 @@ pub unsafe fn draw(root_pane: &mut Pane) { AppPage::TOGGLE => render_toggle_page(app, root_pane), AppPage::CONFIRMATION => todo!() } -} - -pub static BUILD_CONTAINER_PANE: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, _block, parts_build_data_set, build_arg_set, build_res_set, _kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - // Let's create our parent display pane here. - let menu_pane_kind = u32::from_le_bytes([b'p', b'a', b'n', b'1']); - let mut menu_pane_block = ResPane::new(MENU_NAME); - // Overall menu pane @ 0,0 to reason about positions globally - menu_pane_block.set_pos(ResVec3::default()); - let menu_pane = build!(menu_pane_block, ResPane, menu_pane_kind, Pane); - menu_pane.detach(); - - root_pane.append_child(menu_pane); - if MENU_PANE_PTR != menu_pane as *mut Pane as u64 { - MENU_PANE_PTR = menu_pane as *mut Pane as u64; - HAS_SORTED_MENU_CHILDREN = false; - } - - ui::reset_creation(); -}; - -pub static BUILD_FOOTER_BG: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - let menu_pane = root_pane.find_pane_by_name(MENU_NAME, true).unwrap(); - let block = block as *mut ResPictureWithTex<1>; - // For menu backing - let mut pic_menu_block = *block; - pic_menu_block.set_name("trMod_menu_footer_bg"); - let pic_menu_pane = build!(pic_menu_block, ResPictureWithTex<1>, kind, Picture); - pic_menu_pane.detach(); - - menu_pane.append_child(pic_menu_pane); -}; - -pub static BUILD_FOOTER_TXT: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - let menu_pane = root_pane.find_pane_by_name(MENU_NAME, true).unwrap(); - - let block = block as *mut ResTextBox; - let mut text_block = *block; - text_block.set_name("trMod_menu_footer_txt"); - - let text_pane = build!(text_block, ResTextBox, kind, TextBox); - text_pane.set_text_string("Footer!"); - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - text_pane.set_default_material_colors(); - text_pane.set_color(255, 255, 255, 255); - text_pane.detach(); - - menu_pane.append_child(text_pane); -}; - -pub static BUILD_TAB_TXTS: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - (0..NUM_MENU_TABS).for_each(|txt_idx| { - let menu_pane = root_pane.find_pane_by_name(MENU_NAME, true).unwrap(); - - let block = block as *mut ResTextBox; - let mut text_block = *block; - text_block.enable_shadow(); - text_block.text_alignment(TextAlignment::Center); - - let x = txt_idx; - text_block.set_name(menu_tab_fmt!(x)); - - let mut x_offset = x as f32 * 300.0; - // Center current tab since we don't have a help key - if x == 1 { - x_offset -= 25.0; - } - text_block.set_pos(ResVec3::new( - MENU_POS.x - 25.0 + x_offset, - MENU_POS.y + 75.0, - 0.0, - )); - let text_pane = build!(text_block, ResTextBox, kind, TextBox); - text_pane.set_text_string(format!("Tab {txt_idx}!").as_str()); - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - text_pane.set_default_material_colors(); - text_pane.set_color(255, 255, 255, 255); - if txt_idx == 1 { - text_pane.set_color(255, 255, 0, 255); - } - text_pane.set_text_shadow(ResVec2::new(4.0, -3.0), ResVec2::new(1.0, 1.0), [BLACK, BLACK], 0.0); - text_pane.text_outline_enable(true); - text_pane.text_shadow_enable(true); - text_pane.detach(); - menu_pane.append_child(text_pane); - - let mut help_block = *block; - // Font Idx 2 = nintendo64 which contains nice symbols - help_block.font_idx = 2; - - let x = txt_idx; - help_block.set_name(menu_tab_help_fmt!(x)); - - let x_offset = x as f32 * 300.0; - help_block.set_pos(ResVec3::new( - MENU_POS.x - 250.0 + x_offset, - MENU_POS.y + 75.0, - 0.0, - )); - let help_pane = build!(help_block, ResTextBox, kind, TextBox); - help_pane.set_text_string("Help Button"); - let it = help_pane.text_buf as *mut u16; - match txt_idx { - // Left Tab: ZL - 0 => { - *it = 0xE0E6; - *(it.add(1)) = 0x0; - help_pane.text_len = 1; - } - 1 => { - *it = 0x0; - help_pane.text_len = 0; - } - // Right Tab: ZR - 2 => { - *it = 0xE0E7; - *(it.add(1)) = 0x0; - help_pane.text_len = 1; - } - _ => {} - } - - help_pane.set_default_material_colors(); - help_pane.set_color(255, 255, 255, 255); - help_pane.set_text_shadow(ResVec2::new(4.0, -3.0), ResVec2::new(1.0, 1.0), [BLACK, BLACK], 0.0); - help_pane.text_outline_enable(true); - help_pane.text_shadow_enable(true); - help_pane.detach(); - menu_pane.append_child(help_pane); - - // Let's also make defaults help buttons below - text_block.pos.y -= 45.0; - // Uncenter from above - if x == 1 { - text_block.pos.x += 25.0; - } - text_block.set_name(defaults_help_text!(txt_idx)); - let text_pane = build!(text_block, ResTextBox, kind, TextBox); - text_pane.set_text_string(if txt_idx == 0 { - "Save Defaults" - } else if txt_idx == 1 { - "Reset Current Menu" - } else { - "Reset All Menus" - }); - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - text_pane.set_default_material_colors(); - text_pane.set_color(255, 255, 255, 255); - text_pane.set_text_shadow(ResVec2::new(4.0, -3.0), ResVec2::new(1.0, 1.0), [BLACK, BLACK], 0.0); - text_pane.text_outline_enable(true); - text_pane.text_shadow_enable(true); - text_pane.detach(); - menu_pane.append_child(text_pane); - - help_block.pos.y -= 45.0; - help_block.set_name(defaults_help_button!(txt_idx)); - let help_pane = build!(help_block, ResTextBox, kind, TextBox); - help_pane.set_text_string("Help Button"); - let it = help_pane.text_buf as *mut u16; - help_pane.text_len = 1; - *(it.add(1)) = 0x0; - if txt_idx == 0 { - *it = 0xE0E2; // X - } else if txt_idx == 1 { - *it = 0xE0E4; // L - } else { - *it = 0xE0E5; // R - } - - help_pane.set_default_material_colors(); - help_pane.set_color(255, 255, 255, 255); - help_pane.set_text_shadow(ResVec2::new(4.0, -3.0), ResVec2::new(1.0, 1.0), [BLACK, BLACK], 0.0); - help_pane.text_outline_enable(true); - help_pane.text_shadow_enable(true); - help_pane.detach(); - menu_pane.append_child(help_pane); - }); -}; - -pub static BUILD_OPT_TXTS: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - (0..NUM_MENU_TEXT_OPTIONS).for_each(|txt_idx| { - let x = txt_idx % 3; - let y = txt_idx / 3; - - let menu_pane = root_pane.find_pane_by_name(MENU_NAME, true).unwrap(); - - let block = block as *mut ResTextBox; - let mut text_block = *block; - text_block.text_alignment(TextAlignment::Center); - - text_block.set_name(menu_text_name_fmt!(x, y)); - - let x_offset = x as f32 * 500.0; - let y_offset = y as f32 * 85.0; - text_block.set_pos(ResVec3::new( - MENU_POS.x - 480.0 + x_offset, - MENU_POS.y - 50.0 - y_offset, - 0.0, - )); - let text_pane = build!(text_block, ResTextBox, kind, TextBox); - text_pane.set_text_string(format!("Opt {txt_idx}!").as_str()); - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - text_pane.set_default_material_colors(); - text_pane.set_color(85, 89, 92, 255); - text_pane.set_text_shadow(ResVec2::new(4.0, -3.0), ResVec2::new(1.0, 1.0), [BLACK, BLACK], 0.0); - text_pane.set_text_alignment(HorizontalPosition::Center, VerticalPosition::Center); - text_pane.detach(); - menu_pane.append_child(text_pane); - - let mut check_block = *block; - // Font Idx 2 = nintendo64 which contains nice symbols - check_block.font_idx = 2; - - check_block.set_name(menu_text_check_fmt!(x, y)); - check_block.set_pos(ResVec3::new( - MENU_POS.x - 375.0 + x_offset, - MENU_POS.y - 50.0 - y_offset, - 0.0, - )); - let check_pane = build!(check_block, ResTextBox, kind, TextBox); - check_pane.set_text_string(format!("Check {txt_idx}!").as_str()); - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - check_pane.set_default_material_colors(); - check_pane.set_color(0, 0, 0, 255); - check_pane.detach(); - menu_pane.append_child(check_pane); - }); -}; - -pub static BUILD_SLIDER_CONTAINER_PANE: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - let menu_pane = root_pane.find_pane_by_name(MENU_NAME, true).unwrap(); - let slider_ui_root_pane_kind = u32::from_le_bytes([b'p', b'a', b'n', b'1']); - let mut slider_ui_root_block = ResPane::new(SLIDER_MENU_NAME); - - slider_ui_root_block.set_pos(ResVec3::default()); - - let slider_ui_root = build!( - slider_ui_root_block, - ResPane, - slider_ui_root_pane_kind, - Pane - ); - - slider_ui_root.detach(); - menu_pane.append_child(slider_ui_root); - - let block = block as *mut ResPictureWithTex<1>; - - let mut picture_block = *block; - - picture_block.set_name(SLIDER_UI_CONTAINER_NAME); - picture_block.set_size(ResVec2::new(675.0, 300.0)); - picture_block.set_pos(ResVec3::new(-530.0, 180.0, 0.0)); - picture_block.tex_coords = [ - [ResVec2::new(0.0, 0.0)], - [ResVec2::new(1.0, 0.0)], - [ResVec2::new(0.0, 1.5)], - [ResVec2::new(1.0, 1.5)], - ]; - - let picture_pane = build!(picture_block, ResPictureWithTex<1>, kind, Picture); - picture_pane.detach(); - slider_ui_root.append_child(picture_pane); -}; - -pub static BUILD_SLIDER_HEADER_TXT: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - let container_pane = root_pane.find_pane_by_name(SLIDER_MENU_NAME, true).unwrap(); - - let block = block as *mut ResTextBox; - let mut title_block = *block; - - title_block.set_name(SLIDER_TITLE_NAME); - title_block.set_pos(ResVec3::new(-530.0, 285.0, 0.0)); - title_block.set_size(ResVec2::new(550.0, 100.0)); - title_block.font_size = ResVec2::new(50.0, 100.0); - - let title_pane = build!(title_block, ResTextBox, kind, TextBox); - - title_pane.set_text_string("Slider title!"); - - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - title_pane.set_default_material_colors(); - - // Header should be white text - title_pane.set_color(255, 255, 255, 255); - title_pane.detach(); - container_pane.append_child(title_pane); -}; - -pub static BUILD_SLIDER_TXTS: ui::PaneCreationCallback = |_, root_pane, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - (0..NUM_MENU_TEXT_SLIDERS).for_each(|idx| { - let x = idx % 2; - - let label_x_offset = x as f32 * 345.0; - - let slider_root_pane = root_pane.find_pane_by_name(SLIDER_MENU_NAME, true).unwrap(); - let slider_container = root_pane - .find_pane_by_name(SLIDER_UI_CONTAINER_NAME, true) - .unwrap(); - - let block = block as *mut ResTextBox; - - let mut text_block = *block; - - text_block.text_alignment(TextAlignment::Center); - - text_block.set_name(menu_text_slider_fmt!(idx)); - - let value_x_offset = x as f32 * 345.0; - - text_block.set_pos(ResVec3::new( - slider_root_pane.pos_x - 675.0 + value_x_offset, - slider_root_pane.pos_y + (slider_container.size_y * 0.458), - 0.0, - )); - - let text_pane = build!(text_block, ResTextBox, kind, TextBox); - text_pane.set_text_string(format!("Slider opt {idx}!").as_str()); - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - text_pane.set_default_material_colors(); - text_pane.set_color(0, 0, 0, 255); - text_pane.detach(); - slider_root_pane.append_child(text_pane); - - let mut label_block = *block; - - label_block.text_alignment(TextAlignment::Center); - label_block.set_name(menu_slider_label_fmt!(idx)); - label_block.set_pos(ResVec3::new( - slider_root_pane.pos_x - 750.0 + label_x_offset, - slider_root_pane.pos_y + slider_container.size_y * 0.458 + 5.0, - 0.0, - )); - label_block.font_size = ResVec2::new(25.0, 50.0); - - let label_pane = build!(label_block, ResTextBox, kind, TextBox); - - label_pane.set_text_string(format!("Slider opt {idx}!").as_str()); - // Ensure Material Colors are not hardcoded so we can just use SetTextColor. - label_pane.set_default_material_colors(); - label_pane.set_color(85, 89, 92, 255); - label_pane.text_outline_enable(true); - label_pane.set_text_shadow(ResVec2::new(4.0, -3.0), ResVec2::new(1.0, 1.0), [BLACK, BLACK], 0.0); - label_pane.set_text_alignment(HorizontalPosition::Left, VerticalPosition::Top); - label_pane.detach(); - - slider_root_pane.append_child(label_pane); - - }); -}; - -pub static BUILD_BG_LEFTS: ui::PaneCreationCallback = |_, _, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - (0..NUM_MENU_TEXT_OPTIONS).for_each(|txt_idx| { - let x = txt_idx % 3; - let y = txt_idx / 3; - - let x_offset = x as f32 * 500.0; - let y_offset = y as f32 * 85.0; - - let block = block as *mut ResPictureWithTex<2>; - let mut pic_menu_block = *block; - pic_menu_block.set_name(menu_text_bg_left_fmt!(x, y)); - pic_menu_block.picture.scale_x /= 1.5; - pic_menu_block.picture.set_pos(ResVec3::new( - MENU_POS.x - 400.0 - 195.0 + x_offset, - MENU_POS.y - 50.0 - y_offset, - 0.0, - )); - let pic_menu_pane = build!(pic_menu_block, ResPictureWithTex<2>, kind, Picture); - pic_menu_pane.detach(); - if MENU_PANE_PTR != 0 { - (*(MENU_PANE_PTR as *mut Pane)).append_child(pic_menu_pane); - } - }); - - (0..NUM_MENU_TEXT_SLIDERS).for_each(|index| { - let x = index % 2; - - if MENU_PANE_PTR != 0 { - let slider_root = (*(MENU_PANE_PTR as *mut Pane)) - .find_pane_by_name(SLIDER_MENU_NAME, true) - .unwrap(); - let slider_bg = (*(MENU_PANE_PTR as *mut Pane)) - .find_pane_by_name(SLIDER_UI_CONTAINER_NAME, true) - .unwrap(); - let x_offset = x as f32 * 345.0; - - let block = block as *mut ResPictureWithTex<2>; - let mut pic_menu_block = *block; - - pic_menu_block.set_name(menu_slider_button_fg_fmt!(index)); - - pic_menu_block.picture.scale_x /= 1.85; - pic_menu_block.picture.scale_y /= 1.25; - - pic_menu_block.set_pos(ResVec3::new( - slider_root.pos_x - 842.5 + x_offset, - slider_root.pos_y + slider_bg.size_y * 0.458, - 0.0, - )); - - let pic_menu_pane = build!(pic_menu_block, ResPictureWithTex<2>, kind, Picture); - pic_menu_pane.detach(); - - slider_root.append_child(pic_menu_pane); - } - }); -}; - -pub static BUILD_BG_BACKS: ui::PaneCreationCallback = |_, _, original_build, layout, out_build_result_information, device, block, parts_build_data_set, build_arg_set, build_res_set, kind| unsafe { - macro_rules! build { - ($block: ident, $resTyp: ty, $kind:ident, $typ: ty) => { - paste::paste! { - &mut *(original_build(layout, out_build_result_information, device, &mut $block as *mut $resTyp as *mut ResPane, parts_build_data_set, build_arg_set, build_res_set, $kind,) as *mut $typ) - } - }; - } - - (0..NUM_MENU_TEXT_OPTIONS).for_each(|txt_idx| { - let x = txt_idx % 3; - let y = txt_idx / 3; - - let x_offset = x as f32 * 500.0; - let y_offset = y as f32 * 85.0; - - let block = block as *mut ResWindowWithTexCoordsAndFrames<1, 4>; - - let mut bg_block = *block; - bg_block.set_name(menu_text_bg_back_fmt!(x, y)); - bg_block.scale_x /= 2.0; - bg_block.set_pos(ResVec3::new( - MENU_POS.x - 400.0 + x_offset, - MENU_POS.y - 50.0 - y_offset, - 0.0, - )); - let bg_pane = build!(bg_block, ResWindowWithTexCoordsAndFrames<1,4>, kind, Window); - bg_pane.detach(); - if MENU_PANE_PTR != 0 { - (*(MENU_PANE_PTR as *mut Pane)).append_child(bg_pane); - } - }); - - (0..NUM_MENU_TEXT_SLIDERS).for_each(|index| { - let x = index % 2; - - if MENU_PANE_PTR != 0 { - let slider_root = (*(MENU_PANE_PTR as *mut Pane)) - .find_pane_by_name(SLIDER_MENU_NAME, true) - .unwrap(); - let slider_bg = (*(MENU_PANE_PTR as *mut Pane)) - .find_pane_by_name(SLIDER_UI_CONTAINER_NAME, true) - .unwrap(); - - let size_y = 90.0; - - let x_offset = x as f32 * 345.0; - - let block = block as *mut ResWindowWithTexCoordsAndFrames<1, 4>; - let mut bg_block = *block; - - bg_block.set_name(menu_slider_button_label_fmt!(index)); - bg_block.scale_x /= 2.0; - - bg_block.set_size(ResVec2::new(605.0, size_y)); - - bg_block.set_pos(ResVec3::new( - slider_root.pos_x - 700.0 + x_offset, - slider_root.pos_y + slider_bg.size_y * 0.458, - 0.0, - )); - - let bg_pane = build!(bg_block, ResWindowWithTexCoordsAndFrames<1,4>, kind, Window); - bg_pane.detach(); - - slider_root.append_child(bg_pane); - } - }); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/training/ui/mod.rs b/src/training/ui/mod.rs index 48d135bd..051231f8 100644 --- a/src/training/ui/mod.rs +++ b/src/training/ui/mod.rs @@ -1,77 +1,13 @@ use crate::common::{is_ready_go, is_training_mode}; +use sarc::SarcFile; use skyline::nn::ui2d::*; use training_mod_consts::{OnOff, MENU}; -use std::collections::HashMap; -use parking_lot::Mutex; -use skyline::libc::c_void; mod damage; mod display; mod menu; pub mod notifications; -type PaneCreationCallback = for<'a, 'b> unsafe fn(&'a str, &'b mut Pane, - extern "C" fn(*mut Layout, *mut u8, *const u8, *mut ResPane, *const u8, *const u8, *const u8, u32) -> *mut Pane, - *mut Layout, *mut u8, *const u8, *mut ResPane, - *const u8, *const u8, *const u8, u32); -type PaneCreationMap = HashMap< - (String, String), Vec<(bool, PaneCreationCallback)> ->; - -lazy_static::lazy_static! { - static ref PANE_CREATED: Mutex = Mutex::new(HashMap::from([ - ( - (String::from("info_training"), String::from("pic_numbase_01")), - vec![ - (false, menu::BUILD_CONTAINER_PANE), - (false, display::BUILD_PIC_BASE), - (false, menu::BUILD_SLIDER_CONTAINER_PANE), - ] - ), - ( - (String::from("info_training"), String::from("pic_help_bg_00")), - vec![(false, menu::BUILD_FOOTER_BG)] - ), - ( - (String::from("info_training"), String::from("set_txt_help_00")), - vec![(false, menu::BUILD_FOOTER_TXT)] - ), - ( - (String::from("info_training"), String::from("set_txt_num_01")), - vec![ - (false, menu::BUILD_TAB_TXTS), - (false, menu::BUILD_OPT_TXTS), - (false, menu::BUILD_SLIDER_TXTS), - (false, display::BUILD_PANE_TXT), - ] - ), - ( - (String::from("info_training"), String::from("txt_cap_01")), - vec![ - (false, display::BUILD_HEADER_TXT), - (false, menu::BUILD_SLIDER_HEADER_TXT), - ] - ), - ( - (String::from("info_training_btn0_00_item"), String::from("icn_bg_main")), - vec![(false, menu::BUILD_BG_LEFTS)] - ), - ( - (String::from("info_training_btn0_00_item"), String::from("btn_bg")), - vec![(false, menu::BUILD_BG_BACKS)] - ), - ])); -} - -pub unsafe fn reset_creation() { - let pane_created = &mut *PANE_CREATED.data_ptr(); - pane_created.iter_mut().for_each(|(_identifier, creators)| { - creators.iter_mut().for_each(|(created, _callback)| { - *created = false; - }) - }) -} - #[skyline::hook(offset = 0x4b620)] pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64) { let layout_name = &skyline::from_c_str((*layout).layout_name); @@ -94,76 +30,102 @@ pub unsafe fn handle_draw(layout: *mut Layout, draw_info: u64, cmd_buffer: u64) original!()(layout, draw_info, cmd_buffer); } -#[skyline::hook(offset = 0x493a0)] -pub unsafe fn layout_build_parts_impl( - layout: *mut Layout, - out_build_result_information: *mut u8, - device: *const u8, - block: *mut ResPane, - parts_build_data_set: *const u8, - build_arg_set: *const u8, - build_res_set: *const u8, - kind: u32, -) -> *mut Pane { - let layout_name = &skyline::from_c_str((*layout).layout_name); - let root_pane = &mut *(*layout).root_pane; +// We'll keep some sane max size here; we shouldn't reach above 600KiB is the idea, +// but we can try higher if we need to. +#[cfg(feature = "layout_arc_from_file")] +static mut LAYOUT_ARC : &mut [u8; 600000] = &mut [0u8; 600000]; - let block_name = (*block).get_name(); - let identifier = (layout_name.to_string(), block_name); - let pane_created = &mut *PANE_CREATED.data_ptr(); - let panes = pane_created.get_mut(&identifier); - if let Some(panes) = panes { - panes.iter_mut().for_each(|(has_created, callback)| { - if !*has_created { - callback(layout_name, - root_pane, - original!(), - layout, - out_build_result_information, - device, - block, - parts_build_data_set, - build_arg_set, - build_res_set, - kind - ); - - // Special case: Menu init should always occur - if ("info_training".to_string(), "pic_numbase_01".to_string()) != identifier { - *has_created = true; - } - } - }); +/// We are editing the info_training/layout.arc and replacing the original file with our +/// modified version from `sd://TrainingModpack/layout.arc` or, in the case of Ryujinx for the cool +/// kids `${RYUJINX_DIR}/sdcard/TrainingModpack/layout.arc` +/// +/// When we edit the layout we are doing two things. +/// +/// 1. Creating a new BFLYT inside the layout.arc for whatever component we are making. For example, +/// the slider menu button. +/// +/// 2. Adding a Parts pane to the info_training.bflyt with the "Part Name" matching the name of +/// our new BFLYT without the file extension (mimicking how it's done with native Parts panes) +/// +/// # Warnings +/// When creating a BFLYT from an existing one we need to edit the names of the panes so that +/// the default animations no longer modify them. Otherwise the game will override properties, +/// i.e. material colours, and we won't be able to control them properly. +/// +/// Once we have the file edited and saved to the correct location we can access the pane +/// from the layout as we normally would in our Draw function +/// `(root_pane.find_pane_by_name_recursive("name_of_parts_pane")`. +/// +/// # Usage +/// Now say I want to edit background colour of the button's label. +/// I would have to grab the parts pane and find the pane I want to modify on it, then I'd be able +/// to make the modifications as I normally would. +/// +/// ```rust +/// let slider_button = root_pane.find_pane_by_name_recursive("name_of_parts_pane"); +/// let label_bg = slider_button.find_pane_by_name_recursive("name_of_picture_pane"); +/// +/// let label_material = &mut *label_bg.as_picture().material; +/// +/// label_material.set_white_res_color(LABEL_WHITE_SELECTED_COLOR); +/// label_material.set_black_res_color(LABEL_BLACK_SELECTED_COLOR); +/// ``` +#[skyline::hook(offset = 0x37730d4, inline)] +unsafe fn handle_layout_arc_malloc( + ctx: &mut skyline::hooks::InlineCtx +) { + if !is_training_mode() { + return; } - original!()( - layout, - out_build_result_information, - device, - block, - parts_build_data_set, - build_arg_set, - build_res_set, - kind, - ) -} + let decompressed_file = *ctx.registers[21].x.as_ref() as *const u8; + let decompressed_size = *ctx.registers[1].x.as_ref() as usize; + let layout_arc = SarcFile::read( + std::slice::from_raw_parts(decompressed_file,decompressed_size) + ).unwrap(); + let training_layout = layout_arc.files.iter().find(|f| { + f.name.is_some() && f.name.as_ref().unwrap() == &String::from("blyt/info_training.bflyt") + }); + if training_layout.is_none() { + return; + } -#[skyline::hook(offset = 0x32cace0)] -pub unsafe fn handle_load_layout_files( - meta_layout_root: *mut c_void, - loading_list: *mut c_void, - layout_arc_hash: *const u32, - param_4: i32 -) -> u64 { - println!("Layout.arc hash: {:x}", *layout_arc_hash); - original!()(meta_layout_root, loading_list, layout_arc_hash, param_4) + let inject_arc; + let inject_arc_size : u64; + + #[cfg(feature = "layout_arc_from_file")] { + let inject_arc_from_file = std::fs::read("sd:/TrainingModpack/layout.arc").unwrap(); + inject_arc_size = inject_arc_from_file.len() as u64; + + // Copy read file to global + inject_arc_from_file + .iter() + .enumerate() + .for_each(|(idx, byte)| LAYOUT_ARC[idx] = *byte); + inject_arc = LAYOUT_ARC.as_ptr(); + } + + #[cfg(not(feature = "layout_arc_from_file"))] { + include_flate::flate!(static INJECT_ARC_FROM_FILE: [u8] from "src/static/layout.arc"); + + inject_arc = INJECT_ARC_FROM_FILE.as_ptr(); + inject_arc_size = INJECT_ARC_FROM_FILE.len() as u64; + } + + // Decompressed file pointer + let decompressed_file = ctx.registers[21].x.as_mut(); + *decompressed_file = inject_arc as u64; + + // Decompressed size is in each of these registers + *ctx.registers[1].x.as_mut() = inject_arc_size; + *ctx.registers[23].x.as_mut() = inject_arc_size; + *ctx.registers[24].x.as_mut() = inject_arc_size; } pub fn init() { skyline::install_hooks!( handle_draw, - layout_build_parts_impl, - // handle_load_layout_files + handle_layout_arc_malloc ); } diff --git a/training_mod_consts/Cargo.toml b/training_mod_consts/Cargo.toml index b9000f2a..00660439 100644 --- a/training_mod_consts/Cargo.toml +++ b/training_mod_consts/Cargo.toml @@ -10,7 +10,6 @@ strum_macros = "0.21.0" num = "0.4.0" num-derive = "0.3" num-traits = "0.2" -ramhorns = "0.12.0" paste = "1.0" serde = { version = "1.0", features = ["derive"] } serde_repr = "0.1.8" diff --git a/training_mod_consts/src/lib.rs b/training_mod_consts/src/lib.rs index 096e75dc..560837f7 100644 --- a/training_mod_consts/src/lib.rs +++ b/training_mod_consts/src/lib.rs @@ -8,7 +8,6 @@ extern crate bitflags_serde_shim; extern crate num_derive; use core::f64::consts::PI; -use ramhorns::Content; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; #[cfg(feature = "smash")] @@ -1238,7 +1237,7 @@ pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu { pub static mut MENU: TrainingModpackMenu = DEFAULTS_MENU; -#[derive(Content, Clone, Serialize)] +#[derive(Clone, Serialize)] pub struct Slider { pub selected_min: u32, pub selected_max: u32, @@ -1246,14 +1245,14 @@ pub struct Slider { pub abs_max: u32, } -#[derive(Content, Clone, Serialize)] +#[derive(Clone, Serialize)] pub struct Toggle<'a> { pub toggle_value: u32, pub toggle_title: &'a str, pub checked: bool, } -#[derive(Content, Clone, Serialize)] +#[derive(Clone, Serialize)] pub struct SubMenu<'a> { pub submenu_title: &'a str, pub submenu_id: &'a str, @@ -1327,7 +1326,7 @@ impl<'a> SubMenu<'a> { } } -#[derive(Content, Serialize, Clone)] +#[derive(Serialize, Clone)] pub struct Tab<'a> { pub tab_id: &'a str, pub tab_title: &'a str, @@ -1370,7 +1369,7 @@ impl<'a> Tab<'a> { } } -#[derive(Content, Serialize, Clone)] +#[derive(Serialize, Clone)] pub struct UiMenu<'a> { pub tabs: Vec>, }