mirror of
https://github.com/jugeeya/UltimateTrainingModpack.git
synced 2024-11-30 22:00:16 +00:00
Randomized percent on load (#394)
* Create save states tab * Initial work - crashes on boot * Change usizes to u32's. Refactor toggle "checked" logic. Add blujay's panic tracker. * Rename tui mins/maxes * Fix misc. TUI bugs * Fix panic caused by prematurely setting the submenu state to GaugeState::None Set submenu state to GaugeState::MinHover when opening a slider menu so that the slider is immediately loaded When changing from GaugeState::Min/MaxSelected to Min/MaxHover, commit changes from App.current_sub_menu_slider to SubMenu.slider so that it can be exported to JSON * Merge save_damage and save_state_pct_rand_enable settings * Add comments to training_mod_tui::lib.rs * Add icon * Initial work on web slider Todo: Styling polish Bugfix for initial settings load Handle dragging using gamepad * Style and fix web slider * Add separate settings for player random damage * TUI styling fixes * Paginate TUI tabs * Address CR comments
This commit is contained in:
parent
aea5011a89
commit
fa451986e0
17 changed files with 3063 additions and 2004 deletions
|
@ -96,7 +96,7 @@ pub unsafe fn set_menu_from_json(message: &str) {
|
||||||
skyline::error::show_error(
|
skyline::error::show_error(
|
||||||
0x70,
|
0x70,
|
||||||
"Could not parse the menu response!\nPlease send a screenshot of the details page to the developers.\n\0",
|
"Could not parse the menu response!\nPlease send a screenshot of the details page to the developers.\n\0",
|
||||||
&*format!("{:#?}\0", web_response.err().unwrap())
|
&*format!("{:#?}\0", message)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
if MENU.quick_menu == OnOff::Off {
|
if MENU.quick_menu == OnOff::Off {
|
||||||
|
|
21
src/lib.rs
21
src/lib.rs
|
@ -54,6 +54,27 @@ macro_rules! c_str {
|
||||||
|
|
||||||
#[skyline::main(name = "training_modpack")]
|
#[skyline::main(name = "training_modpack")]
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
|
std::panic::set_hook(Box::new(|info| {
|
||||||
|
let location = info.location().unwrap();
|
||||||
|
|
||||||
|
let msg = match info.payload().downcast_ref::<&'static str>() {
|
||||||
|
Some(s) => *s,
|
||||||
|
None => {
|
||||||
|
match info.payload().downcast_ref::<String>() {
|
||||||
|
Some(s) => &s[..],
|
||||||
|
None => "Box<Any>",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let err_msg = format!("thread has panicked at '{}', {}", msg, location);
|
||||||
|
skyline::error::show_error(
|
||||||
|
69,
|
||||||
|
"Skyline plugin has panicked! Please open the details and send a screenshot to the developer, then close the game.\n",
|
||||||
|
err_msg.as_str(),
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
|
||||||
macro_rules! log {
|
macro_rules! log {
|
||||||
($($arg:tt)*) => {
|
($($arg:tt)*) => {
|
||||||
println!("{}{}", "[Training Modpack] ".green(), format!($($arg)*));
|
println!("{}{}", "[Training Modpack] ".green(), format!($($arg)*));
|
||||||
|
|
1
src/static/css/nouislider.min.css
vendored
Normal file
1
src/static/css/nouislider.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative}.noUi-base,.noUi-connects{width:100%;height:100%;position:relative;z-index:1}.noUi-connects{overflow:hidden;z-index:0}.noUi-connect,.noUi-origin{will-change:transform;position:absolute;z-index:1;top:0;right:0;height:100%;width:100%;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;-webkit-transform-style:preserve-3d;transform-origin:0 0;transform-style:flat}.noUi-txt-dir-rtl.noUi-horizontal .noUi-origin{left:0;right:auto}.noUi-vertical .noUi-origin{top:-100%;width:0}.noUi-horizontal .noUi-origin{height:0}.noUi-handle{-webkit-backface-visibility:hidden;backface-visibility:hidden;position:absolute}.noUi-touch-area{height:100%;width:100%}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{-webkit-transition:transform .3s;transition:transform .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;right:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;right:-6px;bottom:-17px}.noUi-txt-dir-rtl.noUi-horizontal .noUi-handle{left:-17px;right:auto}.noUi-target{background:#FAFAFA;border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-connects{border-radius:3px}.noUi-connect{background:#3FB8AF}.noUi-draggable{cursor:ew-resize}.noUi-vertical .noUi-draggable{cursor:ns-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect{background:#B8B8B8}[disabled] .noUi-handle,[disabled].noUi-handle,[disabled].noUi-target{cursor:not-allowed}.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;white-space:nowrap;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-sub{background:#AAA}.noUi-marker-large{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{-webkit-transform:translate(-50%,50%);transform:translate(-50%,50%)}.noUi-rtl .noUi-value-horizontal{-webkit-transform:translate(50%,50%);transform:translate(50%,50%)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{-webkit-transform:translate(0,-50%);transform:translate(0,-50%);padding-left:25px}.noUi-rtl .noUi-value-vertical{-webkit-transform:translate(0,50%);transform:translate(0,50%)}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #D9D9D9;border-radius:3px;background:#fff;color:#000;padding:5px;text-align:center;white-space:nowrap}.noUi-horizontal .noUi-tooltip{-webkit-transform:translate(-50%,0);transform:translate(-50%,0);left:50%;bottom:120%}.noUi-vertical .noUi-tooltip{-webkit-transform:translate(0,-50%);transform:translate(0,-50%);top:50%;right:120%}.noUi-horizontal .noUi-origin>.noUi-tooltip{-webkit-transform:translate(50%,0);transform:translate(50%,0);left:auto;bottom:10px}.noUi-vertical .noUi-origin>.noUi-tooltip{-webkit-transform:translate(0,-18px);transform:translate(0,-18px);top:auto;right:28px}
|
|
@ -185,7 +185,10 @@ body {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
:focus {
|
:focus:not(.noUi-handle),
|
||||||
|
.handleSelected,
|
||||||
|
.noUi-connect
|
||||||
|
{
|
||||||
background: rgb(255, 70, 2);
|
background: rgb(255, 70, 2);
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
45deg,
|
45deg,
|
||||||
|
@ -302,3 +305,38 @@ body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: large;
|
font-size: large;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-slider {
|
||||||
|
width: 70%;
|
||||||
|
margin-bottom: 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-slider-label {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-slider-label > p {
|
||||||
|
font-size: 26px;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: #ccc;
|
||||||
|
border-color: black;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noUi-value, .noUi-pips {
|
||||||
|
margin-top: 12px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noUi-marker, .noUi-marker-large {
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noUi-tooltip {
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
83
src/static/img/save_damage_limits_cpu.svg
Normal file
83
src/static/img/save_damage_limits_cpu.svg
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="80.0px"
|
||||||
|
height="80.0px"
|
||||||
|
viewBox="0 0 80.0 80.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="save_damage_limits.svg"
|
||||||
|
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<defs
|
||||||
|
id="defs5493" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="7.9195959"
|
||||||
|
inkscape:cx="43.373425"
|
||||||
|
inkscape:cy="38.322662"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1904"
|
||||||
|
inkscape:window-height="1001"
|
||||||
|
inkscape:window-x="192"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:pagecheckerboard="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid6063" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5496">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
d="M 15,65 65,15"
|
||||||
|
id="path6070" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||||
|
id="path6072"
|
||||||
|
cx="22.5"
|
||||||
|
cy="22.5"
|
||||||
|
r="7.5" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||||
|
id="path6072-8"
|
||||||
|
cx="60.051399"
|
||||||
|
cy="55.43021"
|
||||||
|
r="7.5" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
d="M 10,5 H 70"
|
||||||
|
id="path993" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 10,75 H 70"
|
||||||
|
id="path993-8" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
83
src/static/img/save_damage_limits_player.svg
Normal file
83
src/static/img/save_damage_limits_player.svg
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="80.0px"
|
||||||
|
height="80.0px"
|
||||||
|
viewBox="0 0 80.0 80.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="save_damage_limits.svg"
|
||||||
|
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<defs
|
||||||
|
id="defs5493" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="7.9195959"
|
||||||
|
inkscape:cx="43.373425"
|
||||||
|
inkscape:cy="38.322662"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1904"
|
||||||
|
inkscape:window-height="1001"
|
||||||
|
inkscape:window-x="192"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:pagecheckerboard="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid6063" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5496">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
d="M 15,65 65,15"
|
||||||
|
id="path6070" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||||
|
id="path6072"
|
||||||
|
cx="22.5"
|
||||||
|
cy="22.5"
|
||||||
|
r="7.5" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||||
|
id="path6072-8"
|
||||||
|
cx="60.051399"
|
||||||
|
cy="55.43021"
|
||||||
|
r="7.5" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
d="M 10,5 H 70"
|
||||||
|
id="path993" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 10,75 H 70"
|
||||||
|
id="path993-8" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
75
src/static/img/save_damage_player.svg
Normal file
75
src/static/img/save_damage_player.svg
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="80.0px"
|
||||||
|
height="80.0px"
|
||||||
|
viewBox="0 0 80.0 80.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="save_damage.svg"
|
||||||
|
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
|
||||||
|
<defs
|
||||||
|
id="defs5493" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#000000"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="7.9195959"
|
||||||
|
inkscape:cx="43.461799"
|
||||||
|
inkscape:cy="28.245216"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1431"
|
||||||
|
inkscape:window-height="1150"
|
||||||
|
inkscape:window-x="192"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid6063" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5496">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:#000000;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
d="M 15,65 65,15"
|
||||||
|
id="path6070" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||||
|
id="path6072"
|
||||||
|
cx="22.5"
|
||||||
|
cy="22.5"
|
||||||
|
r="7.5" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;stroke:#000000;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:fill markers stroke"
|
||||||
|
id="path6072-8"
|
||||||
|
cx="60.051399"
|
||||||
|
cy="55.43021"
|
||||||
|
r="7.5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
1
src/static/js/nouislider.min.js
vendored
Normal file
1
src/static/js/nouislider.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -23,17 +23,31 @@ var DEFAULTS_PREFIX = '__';
|
||||||
|
|
||||||
// Set input handlers
|
// Set input handlers
|
||||||
if (isNx) {
|
if (isNx) {
|
||||||
|
window.nx.footer.setAssign('A', '', function () { select(document.activeElement) }, { se: '' });
|
||||||
window.nx.footer.setAssign('B', '', closeOrExit, { se: '' });
|
window.nx.footer.setAssign('B', '', closeOrExit, { se: '' });
|
||||||
window.nx.footer.setAssign('X', '', resetCurrentMenu, { se: '' });
|
window.nx.footer.setAssign('X', '', resetCurrentMenu, { se: '' });
|
||||||
window.nx.footer.setAssign('L', '', resetAllMenus, { se: '' });
|
window.nx.footer.setAssign('L', '', resetAllMenus, { se: '' });
|
||||||
window.nx.footer.setAssign('R', '', saveDefaults, { se: '' });
|
window.nx.footer.setAssign('R', '', saveDefaults, { se: '' });
|
||||||
window.nx.footer.setAssign('ZR', '', cycleNextTab, { se: '' });
|
window.nx.footer.setAssign('ZR', '', cycleNextTab, { se: '' });
|
||||||
window.nx.footer.setAssign('ZL', '', cyclePrevTab, { se: '' });
|
window.nx.footer.setAssign('ZL', '', cyclePrevTab, { se: '' });
|
||||||
window.nx.addEventListener("message", function(msg) { setSettingsFromJSON(msg)});
|
window.nx.addEventListener("message", function (msg) { setSettingsFromJSON(msg.data) });
|
||||||
window.nx.sendMessage("loaded");
|
document.addEventListener('keydown', (event) => {
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case 37: // Control stick left
|
||||||
|
decreaseSelectedHandle();
|
||||||
|
break;
|
||||||
|
case 39: // Control stick right
|
||||||
|
increaseSelectedHandle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener('keypress', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
|
case 'a':
|
||||||
|
console.log('a');
|
||||||
|
select(document.activeElement);
|
||||||
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
console.log('b');
|
console.log('b');
|
||||||
closeOrExit();
|
closeOrExit();
|
||||||
|
@ -58,6 +72,14 @@ if (isNx) {
|
||||||
console.log('o');
|
console.log('o');
|
||||||
cyclePrevTab();
|
cyclePrevTab();
|
||||||
break;
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
console.log('ArrowLeft');
|
||||||
|
decreaseSelectedHandle();
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
console.log('ArrowRight');
|
||||||
|
increaseSelectedHandle();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -65,6 +87,13 @@ if (isNx) {
|
||||||
const onLoad = () => {
|
const onLoad = () => {
|
||||||
// Activate the first tab
|
// Activate the first tab
|
||||||
openTab(document.querySelector('button.tab-button'));
|
openTab(document.querySelector('button.tab-button'));
|
||||||
|
initializeAllSliders();
|
||||||
|
if (isNx) {
|
||||||
|
window.nx.sendMessage("loaded");
|
||||||
|
} else {
|
||||||
|
settings = {};
|
||||||
|
setSettingsFromJSON("{\"menu\":{\"aerial_delay\":0,\"air_dodge_dir\":0,\"attack_angle\":0,\"buff_state\":0,\"character_item\":0,\"clatter_strength\":0,\"crouch\":0,\"di_state\":0,\"falling_aerials\":0,\"fast_fall_delay\":0,\"fast_fall\":0,\"follow_up\":0,\"frame_advantage\":0,\"full_hop\":0,\"hitbox_vis\":1,\"input_delay\":1,\"ledge_delay\":0,\"ledge_state\":31,\"mash_state\":0,\"mash_triggers\":131,\"miss_tech_state\":15,\"oos_offset\":0,\"pummel_delay\":0,\"quick_menu\":0,\"reaction_time\":0,\"save_damage\":4,\"save_damage_limits\":[63,106],\"save_state_autoload\":1,\"save_state_enable\":1,\"save_state_mirroring\":1,\"sdi_state\":0,\"sdi_strength\":0,\"shield_state\":0,\"shield_tilt\":0,\"stage_hazards\":0,\"tech_state\":15,\"throw_delay\":0,\"throw_state\":1},\"defaults_menu\":{\"aerial_delay\":0,\"air_dodge_dir\":0,\"attack_angle\":0,\"buff_state\":0,\"character_item\":0,\"clatter_strength\":0,\"crouch\":0,\"di_state\":0,\"falling_aerials\":0,\"fast_fall_delay\":0,\"fast_fall\":0,\"follow_up\":0,\"frame_advantage\":0,\"full_hop\":0,\"hitbox_vis\":1,\"input_delay\":1,\"ledge_delay\":0,\"ledge_state\":31,\"mash_state\":0,\"mash_triggers\":131,\"miss_tech_state\":15,\"oos_offset\":0,\"pummel_delay\":0,\"quick_menu\":0,\"reaction_time\":0,\"save_damage\":4,\"save_damage_limits\":[41,118],\"save_state_autoload\":1,\"save_state_enable\":1,\"save_state_mirroring\":1,\"sdi_state\":0,\"sdi_strength\":0,\"shield_state\":0,\"shield_tilt\":0,\"stage_hazards\":0,\"tech_state\":15,\"throw_delay\":0,\"throw_state\":1}}");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onload = onLoad;
|
window.onload = onLoad;
|
||||||
|
@ -73,6 +102,8 @@ var settings;
|
||||||
var defaultSettings;
|
var defaultSettings;
|
||||||
|
|
||||||
var lastFocusedItem = document.querySelector('.menu-item > button');
|
var lastFocusedItem = document.querySelector('.menu-item > button');
|
||||||
|
var selectedSliderHandle = -1;
|
||||||
|
|
||||||
const currentTabContent = () => {
|
const currentTabContent = () => {
|
||||||
const currentActiveTab = document.querySelector('.tab-button.active');
|
const currentActiveTab = document.querySelector('.tab-button.active');
|
||||||
|
|
||||||
|
@ -114,7 +145,11 @@ const openMenuItem = (eventTarget) => {
|
||||||
currentTabContent().classList.toggle('hide');
|
currentTabContent().classList.toggle('hide');
|
||||||
|
|
||||||
modal.classList.toggle('hide');
|
modal.classList.toggle('hide');
|
||||||
modal.querySelector('button').focus();
|
elem = modal.querySelector('button');
|
||||||
|
if (!elem) {
|
||||||
|
elem = modal.querySelector('.noUi-handle-lower')
|
||||||
|
}
|
||||||
|
elem.focus();
|
||||||
|
|
||||||
lastFocusedItem = eventTarget;
|
lastFocusedItem = eventTarget;
|
||||||
};
|
};
|
||||||
|
@ -195,6 +230,19 @@ const exit = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function closeOrExit() {
|
function closeOrExit() {
|
||||||
|
// Deselect any sliders
|
||||||
|
handlesWereSelected = deselectSliderHandles();
|
||||||
|
if (handlesWereSelected) {return}
|
||||||
|
|
||||||
|
selectedHandles = document.querySelectorAll(".handleSelected");
|
||||||
|
if (selectedHandles.length) {
|
||||||
|
console.log("Found selected handles");
|
||||||
|
selectedHandles.forEach((handle) => {
|
||||||
|
handle.classList.remove("handleSelected");
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Close any open menus
|
// Close any open menus
|
||||||
if (document.querySelector('.modal:not(.hide)')) {
|
if (document.querySelector('.modal:not(.hide)')) {
|
||||||
console.log('Closing Items');
|
console.log('Closing Items');
|
||||||
|
@ -210,52 +258,12 @@ function closeOrExit() {
|
||||||
|
|
||||||
function setSettingsFromJSON(msg) {
|
function setSettingsFromJSON(msg) {
|
||||||
// Receive a menu message and set settings
|
// Receive a menu message and set settings
|
||||||
var msg_json = JSON.parse(msg.data);
|
var msg_json = JSON.parse(msg);
|
||||||
settings = msg_json["menu"];
|
settings = msg_json["menu"];
|
||||||
defaultSettings = msg_json["defaults_menu"];
|
defaultSettings = msg_json["defaults_menu"];
|
||||||
populateMenuFromSettings();
|
populateMenuFromSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSettingsFromURL() {
|
|
||||||
var { search } = window.location;
|
|
||||||
// Actual settings
|
|
||||||
const settingsFromSearch = search
|
|
||||||
.replace('?', '')
|
|
||||||
.split('&')
|
|
||||||
.reduce((accumulator, currentValue) => {
|
|
||||||
var [key, value] = currentValue.split('=');
|
|
||||||
if (!key.startsWith('__')) {
|
|
||||||
accumulator[key] = parseInt(value);
|
|
||||||
}
|
|
||||||
return accumulator;
|
|
||||||
}, {});
|
|
||||||
settings = settingsFromSearch;
|
|
||||||
|
|
||||||
// Default settings
|
|
||||||
const defaultSettingsFromSearch = search
|
|
||||||
.replace('?', '')
|
|
||||||
.split('&')
|
|
||||||
.reduce((accumulator, currentValue) => {
|
|
||||||
var [key, value] = currentValue.split('=');
|
|
||||||
if (key.startsWith('__')) {
|
|
||||||
accumulator[key.replace('__','')] = parseInt(value);
|
|
||||||
}
|
|
||||||
return accumulator;
|
|
||||||
}, {});
|
|
||||||
defaultSettings = defaultSettingsFromSearch;
|
|
||||||
populateMenuFromSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildURLFromSettings() {
|
|
||||||
const url = 'http://localhost/?';
|
|
||||||
|
|
||||||
const urlParams = Object.entries(settings).map((setting) => {
|
|
||||||
return `${setting[0]}=${setting[1]}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return url + urlParams.join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectSingleOption(eventTarget) {
|
function selectSingleOption(eventTarget) {
|
||||||
// Deselect all options in the submenu
|
// Deselect all options in the submenu
|
||||||
const parent = eventTarget.parentElement;
|
const parent = eventTarget.parentElement;
|
||||||
|
@ -277,19 +285,26 @@ const isValueInBitmask = (value, mask) => (mask & value) != 0;
|
||||||
const setOptionsForMenu = (menuId) => {
|
const setOptionsForMenu = (menuId) => {
|
||||||
const modal = document.querySelector(`.modal[data-id="${menuId}"]`);
|
const modal = document.querySelector(`.modal[data-id="${menuId}"]`);
|
||||||
|
|
||||||
modal.querySelectorAll('.menu-icon').forEach(function (toggle) {
|
if (modal.querySelector('.modal-button')) {
|
||||||
if (isValueInBitmask(toggle.dataset.val, settings[menuId])) {
|
// Toggle menu
|
||||||
toggle.classList.remove('hidden');
|
modal.querySelectorAll('.menu-icon').forEach(function (toggle) {
|
||||||
} else {
|
if (isValueInBitmask(toggle.dataset.val, settings[menuId])) {
|
||||||
toggle.classList.add('hidden');
|
toggle.classList.remove('hidden');
|
||||||
}
|
} else {
|
||||||
});
|
toggle.classList.add('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (modal.classList.contains('single-option')) {
|
if (modal.classList.contains('single-option')) {
|
||||||
// If no option is selected default to the first option
|
// If no option is selected default to the first option
|
||||||
if (modal.querySelectorAll('.menu-icon:not(.hidden)').length === 0) {
|
if (modal.querySelectorAll('.menu-icon:not(.hidden)').length === 0) {
|
||||||
selectSingleOption(modal.querySelector('button'));
|
selectSingleOption(modal.querySelector('button'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Slider menu
|
||||||
|
slider = modal.querySelector('.modal-slider');
|
||||||
|
setSliderVals(slider, settings[menuId]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -297,17 +312,25 @@ function populateMenuFromSettings() {
|
||||||
document.querySelectorAll('.menu-item').forEach((item) => setOptionsForMenu(item.id));
|
document.querySelectorAll('.menu-item').forEach((item) => setOptionsForMenu(item.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMaskFromMenuID(id) {
|
function getSettingsValFromMenuID(id) {
|
||||||
var value = 0;
|
|
||||||
const modal = document.querySelector(`.modal[data-id='${id}']`);
|
const modal = document.querySelector(`.modal[data-id='${id}']`);
|
||||||
|
|
||||||
const options = modal.querySelectorAll('img:not(.hidden)');
|
if (modal.querySelector('.modal-button')) {
|
||||||
|
// Toggle menu
|
||||||
|
// Return value is a bitmask
|
||||||
|
var value = 0;
|
||||||
|
const options = modal.querySelectorAll('img:not(.hidden)');
|
||||||
|
|
||||||
options.forEach(function (toggle) {
|
options.forEach(function (toggle) {
|
||||||
value += parseInt(toggle.dataset.val);
|
value += parseInt(toggle.dataset.val);
|
||||||
});
|
});
|
||||||
|
return value;
|
||||||
return value;
|
} else {
|
||||||
|
// Slider menu
|
||||||
|
// Return value is a [lower,upper] array
|
||||||
|
slider = modal.querySelector('.modal-slider');
|
||||||
|
return getSliderVals(slider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetCurrentMenu() {
|
function resetCurrentMenu() {
|
||||||
|
@ -315,10 +338,11 @@ function resetCurrentMenu() {
|
||||||
const menu = document.querySelector('.modal:not(.hide)');
|
const menu = document.querySelector('.modal:not(.hide)');
|
||||||
|
|
||||||
const menuId = menu.dataset.id;
|
const menuId = menu.dataset.id;
|
||||||
const defaultSectionMask = defaultSettings[menuId];
|
const defaultSubmenuSetting = defaultSettings[menuId];
|
||||||
|
|
||||||
settings[menuId] = defaultSectionMask;
|
settings[menuId] = defaultSubmenuSetting;
|
||||||
|
|
||||||
|
deselectSliderHandles();
|
||||||
populateMenuFromSettings();
|
populateMenuFromSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,10 +351,11 @@ function resetAllMenus() {
|
||||||
if (confirm('Are you sure that you want to reset all menu settings to the default?')) {
|
if (confirm('Are you sure that you want to reset all menu settings to the default?')) {
|
||||||
document.querySelectorAll('.menu-item').forEach(function (item) {
|
document.querySelectorAll('.menu-item').forEach(function (item) {
|
||||||
const defaultMenuId = item.id;
|
const defaultMenuId = item.id;
|
||||||
const defaultMask = defaultSettings[defaultMenuId];
|
const defaultSubmenuSetting = defaultSettings[defaultMenuId];
|
||||||
|
|
||||||
settings[item.id] = defaultMask;
|
settings[item.id] = defaultSubmenuSetting;
|
||||||
|
|
||||||
|
deselectSliderHandles();
|
||||||
populateMenuFromSettings();
|
populateMenuFromSettings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -344,13 +369,13 @@ function saveDefaults() {
|
||||||
if (confirm('Are you sure that you want to change the default menu settings to the current selections?')) {
|
if (confirm('Are you sure that you want to change the default menu settings to the current selections?')) {
|
||||||
document.querySelectorAll('.menu-item').forEach((item) => {
|
document.querySelectorAll('.menu-item').forEach((item) => {
|
||||||
const menu = item.id;
|
const menu = item.id;
|
||||||
|
defaultSettings[menu] = getSettingsValFromMenuID(item.id);
|
||||||
defaultSettings[menu] = getMaskFromMenuID(item.id);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleNextTab() {
|
function cycleNextTab() {
|
||||||
|
deselectSliderHandles();
|
||||||
// Cycle to the next tab
|
// Cycle to the next tab
|
||||||
const activeTab = document.querySelector('.tab-button.active');
|
const activeTab = document.querySelector('.tab-button.active');
|
||||||
var nextTab = activeTab.nextElementSibling;
|
var nextTab = activeTab.nextElementSibling;
|
||||||
|
@ -362,6 +387,7 @@ function cycleNextTab() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cyclePrevTab() {
|
function cyclePrevTab() {
|
||||||
|
deselectSliderHandles();
|
||||||
// Cycle to the previous tab
|
// Cycle to the previous tab
|
||||||
const activeTab = document.querySelector('.tab-button.active');
|
const activeTab = document.querySelector('.tab-button.active');
|
||||||
var previousTab = activeTab.previousElementSibling;
|
var previousTab = activeTab.previousElementSibling;
|
||||||
|
@ -372,3 +398,124 @@ function cyclePrevTab() {
|
||||||
}
|
}
|
||||||
openTab(previousTab);
|
openTab(previousTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSliderVals(slider) {
|
||||||
|
var arr = slider.noUiSlider.get();
|
||||||
|
return [parseFloat(arr[0]), parseFloat(arr[1])]
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSliderVals(slider, vals) {
|
||||||
|
slider.noUiSlider.set(vals);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSettingsFromSlider(slider) {
|
||||||
|
menuId = closestClass(slider, "modal").dataset.id;
|
||||||
|
settings[menuId] = getSliderVals(slider)
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeSlider(slider) {
|
||||||
|
noUiSlider.create(
|
||||||
|
slider,
|
||||||
|
{
|
||||||
|
start: [
|
||||||
|
parseFloat(slider.dataset.selectedMin),
|
||||||
|
parseFloat(slider.dataset.selectedMax),
|
||||||
|
],
|
||||||
|
connect: true,
|
||||||
|
range: {
|
||||||
|
'min': parseFloat(slider.dataset.absMin),
|
||||||
|
'max': parseFloat(slider.dataset.absMax),
|
||||||
|
},
|
||||||
|
step: 1,
|
||||||
|
tooltips: [
|
||||||
|
{ to: function (value) { return value.toFixed(0) + '%'; } },
|
||||||
|
{ to: function (value) { return value.toFixed(0) + '%'; } },
|
||||||
|
],
|
||||||
|
pips: {
|
||||||
|
mode: 'range',
|
||||||
|
density: 10,
|
||||||
|
},
|
||||||
|
keyboardMultiplier: 0, // Prevents doublestepping with custom implementation
|
||||||
|
}
|
||||||
|
);
|
||||||
|
slider.noUiSlider.on('set', function () { setSettingsFromSlider(slider) });
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeAllSliders() {
|
||||||
|
document.querySelectorAll(".modal-slider").forEach((item) => {
|
||||||
|
initializeSlider(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function select(element) {
|
||||||
|
if (element.classList.contains("noUi-handle")) {
|
||||||
|
element.classList.toggle("handleSelected");
|
||||||
|
}
|
||||||
|
element.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function increaseSelectedHandle() {
|
||||||
|
// Increments the selected slider handle, if one is selected
|
||||||
|
// Won't go past the slider limit
|
||||||
|
handle = document.querySelector(".noUi-handle.handleSelected");
|
||||||
|
if (handle) {
|
||||||
|
slider = closestClass(handle, "modal-slider");
|
||||||
|
isLowerHandle = handle.classList.contains("noUi-handle-lower");
|
||||||
|
step = slider.noUiSlider.options.step;
|
||||||
|
currentVals = getSliderVals(slider);
|
||||||
|
if (isLowerHandle) {
|
||||||
|
setSliderVals(
|
||||||
|
slider,
|
||||||
|
[currentVals[0] + step, null]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setSliderVals(
|
||||||
|
slider,
|
||||||
|
[null, currentVals[1] + step]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Refocus the handle, since the native navigation might focus the other handle
|
||||||
|
// TODO: Is there a more elegant way to do this?
|
||||||
|
setTimeout( function() {handle.focus() }, 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decreaseSelectedHandle() {
|
||||||
|
// Decrements the selected slider handle, if one is selected
|
||||||
|
// Won't go past the slider limit
|
||||||
|
handle = document.querySelector(".noUi-handle.handleSelected");
|
||||||
|
if (handle) {
|
||||||
|
slider = closestClass(handle, "modal-slider");
|
||||||
|
isLowerHandle = handle.classList.contains("noUi-handle-lower");
|
||||||
|
step = slider.noUiSlider.options.step;
|
||||||
|
currentVals = getSliderVals(slider);
|
||||||
|
if (isLowerHandle) {
|
||||||
|
setSliderVals(
|
||||||
|
slider,
|
||||||
|
[currentVals[0] - step, null]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setSliderVals(
|
||||||
|
slider,
|
||||||
|
[null, currentVals[1] - step]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Refocus the handle, since the native navigation might focus the other handle
|
||||||
|
// TODO: Is there a more elegant way to do this?
|
||||||
|
setTimeout( function() {handle.focus() }, 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deselectSliderHandles() {
|
||||||
|
/// Returns true if any slider handles were changed from selected -> deselected
|
||||||
|
/// Returns false if there were no selected slider handles to begin with
|
||||||
|
selectedHandles = document.querySelectorAll(".handleSelected");
|
||||||
|
if (selectedHandles.length) {
|
||||||
|
selectedHandles.forEach((handle) => {
|
||||||
|
handle.classList.remove("handleSelected");
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
<title>Modpack Menu</title>
|
<title>Modpack Menu</title>
|
||||||
|
<link rel="stylesheet" href="./css/nouislider.min.css">
|
||||||
<link rel="stylesheet" href="./css/training_modpack.css" />
|
<link rel="stylesheet" href="./css/training_modpack.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<script defer src="./js/nouislider.min.js"></script>
|
||||||
<script defer src="./js/training_modpack.js"></script>
|
<script defer src="./js/training_modpack.js"></script>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<a id="ret-button" tabindex="-1" class="return-icon-container" onclick="closeOrExit()">
|
<a id="ret-button" tabindex="-1" class="return-icon-container" onclick="closeOrExit()">
|
||||||
|
@ -42,6 +44,20 @@
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{{/toggles}}
|
{{/toggles}}
|
||||||
|
{{#slider}}
|
||||||
|
<div class="modal-slider-label">
|
||||||
|
<p>{{submenu_title}}</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="{{submenu_id}}-slider"
|
||||||
|
data-selected-min="{{selected_min}}"
|
||||||
|
data-selected-max="{{selected_max}}"
|
||||||
|
data-abs-min="{{abs_min}}"
|
||||||
|
data-abs-max="{{abs_max}}"
|
||||||
|
class="modal-slider"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{{/slider}}
|
||||||
</div>
|
</div>
|
||||||
{{/tab_submenus}}
|
{{/tab_submenus}}
|
||||||
<div id="{{tab_id}}_tab" class="tab-content hide">
|
<div id="{{tab_id}}_tab" class="tab-content hide">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::common::button_config;
|
use crate::common::button_config;
|
||||||
|
use crate::common::consts::get_random_float;
|
||||||
use crate::common::consts::get_random_int;
|
use crate::common::consts::get_random_int;
|
||||||
use crate::common::consts::FighterId;
|
use crate::common::consts::FighterId;
|
||||||
use crate::common::consts::OnOff;
|
use crate::common::consts::OnOff;
|
||||||
|
@ -15,7 +16,7 @@ use smash::app::{self, lua_bind::*, Item};
|
||||||
use smash::hash40;
|
use smash::hash40;
|
||||||
use smash::lib::lua_const::*;
|
use smash::lib::lua_const::*;
|
||||||
use smash::phx::{Hash40, Vector3f};
|
use smash::phx::{Hash40, Vector3f};
|
||||||
use training_mod_consts::CharacterItem;
|
use training_mod_consts::{CharacterItem, SaveDamage};
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
enum SaveState {
|
enum SaveState {
|
||||||
|
@ -149,16 +150,6 @@ pub unsafe fn get_param_int(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_damage(module_accessor: &mut app::BattleObjectModuleAccessor, damage: f32) {
|
fn set_damage(module_accessor: &mut app::BattleObjectModuleAccessor, damage: f32) {
|
||||||
let overwrite_damage;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
overwrite_damage = MENU.save_damage == OnOff::On;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !overwrite_damage {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
DamageModule::heal(
|
DamageModule::heal(
|
||||||
module_accessor,
|
module_accessor,
|
||||||
|
@ -365,7 +356,41 @@ pub unsafe fn save_states(module_accessor: &mut app::BattleObjectModuleAccessor)
|
||||||
|
|
||||||
// If we're done moving, reset percent, handle charges, and apply buffs
|
// If we're done moving, reset percent, handle charges, and apply buffs
|
||||||
if save_state.state == NoAction {
|
if save_state.state == NoAction {
|
||||||
set_damage(module_accessor, save_state.percent);
|
// Set damage of the save state
|
||||||
|
if !is_cpu {
|
||||||
|
match MENU.save_damage_player {
|
||||||
|
SaveDamage::SAVED => {
|
||||||
|
set_damage(module_accessor, save_state.percent);
|
||||||
|
}
|
||||||
|
SaveDamage::RANDOM => {
|
||||||
|
// Gen random value
|
||||||
|
let pct: f32 = get_random_float(
|
||||||
|
MENU.save_damage_limits_player.0 as f32,
|
||||||
|
MENU.save_damage_limits_player.1 as f32,
|
||||||
|
);
|
||||||
|
set_damage(module_accessor, pct);
|
||||||
|
}
|
||||||
|
SaveDamage::DEFAULT => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match MENU.save_damage_cpu {
|
||||||
|
SaveDamage::SAVED => {
|
||||||
|
set_damage(module_accessor, save_state.percent);
|
||||||
|
}
|
||||||
|
SaveDamage::RANDOM => {
|
||||||
|
// Gen random value
|
||||||
|
let pct: f32 = get_random_float(
|
||||||
|
MENU.save_damage_limits_cpu.0 as f32,
|
||||||
|
MENU.save_damage_limits_cpu.1 as f32,
|
||||||
|
);
|
||||||
|
set_damage(module_accessor, pct);
|
||||||
|
}
|
||||||
|
SaveDamage::DEFAULT => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set to held item
|
// Set to held item
|
||||||
if !is_cpu && !fighter_is_nana && MENU.character_item != CharacterItem::None {
|
if !is_cpu && !fighter_is_nana && MENU.character_item != CharacterItem::None {
|
||||||
apply_item(MENU.character_item);
|
apply_item(MENU.character_item);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#![feature(const_option)]
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
|
|
||||||
|
@ -14,13 +13,16 @@ 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::collections::HashMap;
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
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<usize>;
|
fn to_toggle_vals() -> Vec<u32>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SliderTrait {
|
||||||
|
fn get_limits() -> (u32, u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
// bitflag helper function macro
|
// bitflag helper function macro
|
||||||
|
@ -73,9 +75,9 @@ macro_rules! extra_bitflag_impls {
|
||||||
all_options.iter().map(|i| i.as_str().unwrap_or("")).collect()
|
all_options.iter().map(|i| i.as_str().unwrap_or("")).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_toggle_vals() -> Vec<usize> {
|
fn to_toggle_vals() -> Vec<u32> {
|
||||||
let all_options = <$e>::all().to_vec();
|
let all_options = <$e>::all().to_vec();
|
||||||
all_options.iter().map(|i| i.bits() as usize).collect()
|
all_options.iter().map(|i| i.bits() as u32).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +93,18 @@ pub fn get_random_int(_max: i32) -> i32 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a random float between _min and _max.
|
||||||
|
/// Note that (_min <= _max) is not enforced.
|
||||||
|
pub fn get_random_float(_min: f32, _max: f32) -> f32 {
|
||||||
|
#[cfg(feature = "smash")]
|
||||||
|
unsafe {
|
||||||
|
_min + smash::app::sv_math::randf(smash::hash40("fighter"), _max - _min)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "smash"))]
|
||||||
|
_min
|
||||||
|
}
|
||||||
|
|
||||||
pub fn random_option<T>(arg: &[T]) -> &T {
|
pub fn random_option<T>(arg: &[T]) -> &T {
|
||||||
&arg[get_random_int(arg.len() as i32) as usize]
|
&arg[get_random_int(arg.len() as i32) as usize]
|
||||||
}
|
}
|
||||||
|
@ -289,8 +303,8 @@ impl ToggleTrait for Shield {
|
||||||
Shield::iter().map(|i| i.as_str().unwrap_or("")).collect()
|
Shield::iter().map(|i| i.as_str().unwrap_or("")).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_toggle_vals() -> Vec<usize> {
|
fn to_toggle_vals() -> Vec<u32> {
|
||||||
Shield::iter().map(|i| i as usize).collect()
|
Shield::iter().map(|i| i as u32).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,8 +336,8 @@ impl ToggleTrait for SaveStateMirroring {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_toggle_vals() -> Vec<usize> {
|
fn to_toggle_vals() -> Vec<u32> {
|
||||||
SaveStateMirroring::iter().map(|i| i as usize).collect()
|
SaveStateMirroring::iter().map(|i| i as u32).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,7 +369,7 @@ impl ToggleTrait for OnOff {
|
||||||
fn to_toggle_strs() -> Vec<&'static str> {
|
fn to_toggle_strs() -> Vec<&'static str> {
|
||||||
vec!["Off", "On"]
|
vec!["Off", "On"]
|
||||||
}
|
}
|
||||||
fn to_toggle_vals() -> Vec<usize> {
|
fn to_toggle_vals() -> Vec<u32> {
|
||||||
vec![0, 1]
|
vec![0, 1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -875,8 +889,8 @@ impl ToggleTrait for InputFrequency {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_toggle_vals() -> Vec<usize> {
|
fn to_toggle_vals() -> Vec<u32> {
|
||||||
InputFrequency::iter().map(|i| i as usize).collect()
|
InputFrequency::iter().map(|i| i as u32).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -940,8 +954,8 @@ impl ToggleTrait for CharacterItem {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_toggle_vals() -> Vec<usize> {
|
fn to_toggle_vals() -> Vec<u32> {
|
||||||
CharacterItem::iter().map(|i| i as usize).collect()
|
CharacterItem::iter().map(|i| i as u32).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -993,17 +1007,56 @@ impl MashTrigger {
|
||||||
|
|
||||||
const fn default() -> MashTrigger {
|
const fn default() -> MashTrigger {
|
||||||
// Hit, block, clatter
|
// Hit, block, clatter
|
||||||
MashTrigger::HIT.union(MashTrigger::BLOCK).union(MashTrigger::CLATTER)
|
MashTrigger::HIT
|
||||||
|
.union(MashTrigger::BLOCK)
|
||||||
|
.union(MashTrigger::CLATTER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extra_bitflag_impls! {MashTrigger}
|
extra_bitflag_impls! {MashTrigger}
|
||||||
impl_serde_for_bitflags!(MashTrigger);
|
impl_serde_for_bitflags!(MashTrigger);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DamagePercent(pub u32, pub u32);
|
||||||
|
|
||||||
|
impl SliderTrait for DamagePercent {
|
||||||
|
fn get_limits() -> (u32, u32) {
|
||||||
|
(0, 150)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DamagePercent {
|
||||||
|
const fn default() -> DamagePercent {
|
||||||
|
DamagePercent(0, 150)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
pub struct SaveDamage : u32
|
||||||
|
{
|
||||||
|
const DEFAULT = 0b001;
|
||||||
|
const SAVED = 0b010;
|
||||||
|
const RANDOM = 0b100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SaveDamage {
|
||||||
|
fn as_str(self) -> Option<&'static str> {
|
||||||
|
Some(match self {
|
||||||
|
SaveDamage::DEFAULT => "Default",
|
||||||
|
SaveDamage::SAVED => "Save State",
|
||||||
|
SaveDamage::RANDOM => "Random Value",
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extra_bitflag_impls! {SaveDamage}
|
||||||
|
impl_serde_for_bitflags!(SaveDamage);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||||
pub struct TrainingModpackMenu {
|
pub struct TrainingModpackMenu {
|
||||||
// Mash Tab
|
|
||||||
pub aerial_delay: Delay,
|
pub aerial_delay: Delay,
|
||||||
pub air_dodge_dir: Direction,
|
pub air_dodge_dir: Direction,
|
||||||
pub attack_angle: AttackAngle,
|
pub attack_angle: AttackAngle,
|
||||||
|
@ -1029,7 +1082,10 @@ pub struct TrainingModpackMenu {
|
||||||
pub pummel_delay: MedDelay,
|
pub pummel_delay: MedDelay,
|
||||||
pub quick_menu: OnOff,
|
pub quick_menu: OnOff,
|
||||||
pub reaction_time: Delay,
|
pub reaction_time: Delay,
|
||||||
pub save_damage: OnOff,
|
pub save_damage_cpu: SaveDamage,
|
||||||
|
pub save_damage_limits_cpu: DamagePercent,
|
||||||
|
pub save_damage_player: SaveDamage,
|
||||||
|
pub save_damage_limits_player: DamagePercent,
|
||||||
pub save_state_autoload: OnOff,
|
pub save_state_autoload: OnOff,
|
||||||
pub save_state_enable: OnOff,
|
pub save_state_enable: OnOff,
|
||||||
pub save_state_mirroring: SaveStateMirroring,
|
pub save_state_mirroring: SaveStateMirroring,
|
||||||
|
@ -1043,71 +1099,15 @@ pub struct TrainingModpackMenu {
|
||||||
pub throw_state: ThrowOption,
|
pub throw_state: ThrowOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! set_by_str {
|
const fn num_bits<T>() -> u32 {
|
||||||
($obj:ident, $s:ident, $($field:ident = $rhs:expr,)*) => {
|
(std::mem::size_of::<T>() * 8) as u32
|
||||||
$(
|
|
||||||
if $s == stringify!($field) {
|
|
||||||
$obj.$field = $rhs.unwrap();
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn num_bits<T>() -> usize {
|
|
||||||
std::mem::size_of::<T>() * 8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_2(x: u32) -> u32 {
|
fn log_2(x: u32) -> u32 {
|
||||||
if x == 0 {
|
if x == 0 {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
num_bits::<u32>() as u32 - x.leading_zeros() - 1
|
num_bits::<u32>() - x.leading_zeros() - 1
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TrainingModpackMenu {
|
|
||||||
pub fn set(&mut self, s: &str, val: u32) {
|
|
||||||
set_by_str!(
|
|
||||||
self,
|
|
||||||
s,
|
|
||||||
aerial_delay = Delay::from_bits(val),
|
|
||||||
air_dodge_dir = Direction::from_bits(val),
|
|
||||||
attack_angle = AttackAngle::from_bits(val),
|
|
||||||
clatter_strength = num::FromPrimitive::from_u32(val),
|
|
||||||
crouch = OnOff::from_val(val),
|
|
||||||
di_state = Direction::from_bits(val),
|
|
||||||
falling_aerials = BoolFlag::from_bits(val),
|
|
||||||
fast_fall_delay = Delay::from_bits(val),
|
|
||||||
fast_fall = BoolFlag::from_bits(val),
|
|
||||||
follow_up = Action::from_bits(val),
|
|
||||||
full_hop = BoolFlag::from_bits(val),
|
|
||||||
hitbox_vis = OnOff::from_val(val),
|
|
||||||
input_delay = Delay::from_bits(val),
|
|
||||||
ledge_delay = LongDelay::from_bits(val),
|
|
||||||
ledge_state = LedgeOption::from_bits(val),
|
|
||||||
mash_state = Action::from_bits(val),
|
|
||||||
mash_triggers = MashTrigger::from_bits(val),
|
|
||||||
miss_tech_state = MissTechFlags::from_bits(val),
|
|
||||||
oos_offset = Delay::from_bits(val),
|
|
||||||
reaction_time = Delay::from_bits(val),
|
|
||||||
sdi_state = Direction::from_bits(val),
|
|
||||||
sdi_strength = num::FromPrimitive::from_u32(val),
|
|
||||||
shield_state = num::FromPrimitive::from_u32(val),
|
|
||||||
shield_tilt = Direction::from_bits(val),
|
|
||||||
stage_hazards = OnOff::from_val(val),
|
|
||||||
tech_state = TechFlags::from_bits(val),
|
|
||||||
save_damage = OnOff::from_val(val),
|
|
||||||
frame_advantage = OnOff::from_val(val),
|
|
||||||
save_state_mirroring = num::FromPrimitive::from_u32(val),
|
|
||||||
save_state_enable = OnOff::from_val(val),
|
|
||||||
save_state_autoload = OnOff::from_val(val),
|
|
||||||
throw_state = ThrowOption::from_bits(val),
|
|
||||||
throw_delay = MedDelay::from_bits(val),
|
|
||||||
pummel_delay = MedDelay::from_bits(val),
|
|
||||||
buff_state = BuffOption::from_bits(val),
|
|
||||||
character_item = num::FromPrimitive::from_u32(val),
|
|
||||||
quick_menu = OnOff::from_val(val),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1169,7 +1169,10 @@ pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu {
|
||||||
pummel_delay: MedDelay::empty(),
|
pummel_delay: MedDelay::empty(),
|
||||||
quick_menu: OnOff::Off,
|
quick_menu: OnOff::Off,
|
||||||
reaction_time: Delay::empty(),
|
reaction_time: Delay::empty(),
|
||||||
save_damage: OnOff::On,
|
save_damage_cpu: SaveDamage::DEFAULT,
|
||||||
|
save_damage_limits_cpu: DamagePercent::default(),
|
||||||
|
save_damage_player: SaveDamage::DEFAULT,
|
||||||
|
save_damage_limits_player: DamagePercent::default(),
|
||||||
save_state_autoload: OnOff::Off,
|
save_state_autoload: OnOff::Off,
|
||||||
save_state_enable: OnOff::On,
|
save_state_enable: OnOff::On,
|
||||||
save_state_mirroring: SaveStateMirroring::None,
|
save_state_mirroring: SaveStateMirroring::None,
|
||||||
|
@ -1185,37 +1188,38 @@ pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu {
|
||||||
|
|
||||||
pub static mut MENU: TrainingModpackMenu = DEFAULTS_MENU;
|
pub static mut MENU: TrainingModpackMenu = DEFAULTS_MENU;
|
||||||
|
|
||||||
#[derive(Content, Clone)]
|
#[derive(Content, Clone, Serialize)]
|
||||||
pub struct Slider {
|
pub struct Slider {
|
||||||
pub min: usize,
|
pub selected_min: u32,
|
||||||
pub max: usize,
|
pub selected_max: u32,
|
||||||
pub index: usize,
|
pub abs_min: u32,
|
||||||
pub value: usize,
|
pub abs_max: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content, Clone)]
|
#[derive(Content, Clone, Serialize)]
|
||||||
pub struct Toggle<'a> {
|
pub struct Toggle<'a> {
|
||||||
pub toggle_value: usize,
|
pub toggle_value: u32,
|
||||||
pub toggle_title: &'a str,
|
pub toggle_title: &'a str,
|
||||||
pub checked: bool,
|
pub checked: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content, Clone)]
|
#[derive(Content, Clone, Serialize)]
|
||||||
pub struct SubMenu<'a> {
|
pub struct SubMenu<'a> {
|
||||||
pub submenu_title: &'a str,
|
pub submenu_title: &'a str,
|
||||||
pub submenu_id: &'a str,
|
pub submenu_id: &'a str,
|
||||||
pub help_text: &'a str,
|
pub help_text: &'a str,
|
||||||
pub is_single_option: bool,
|
pub is_single_option: bool,
|
||||||
pub toggles: Vec<Toggle<'a>>,
|
pub toggles: Vec<Toggle<'a>>,
|
||||||
|
pub slider: Option<Slider>,
|
||||||
pub _type: &'a str,
|
pub _type: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SubMenu<'a> {
|
impl<'a> SubMenu<'a> {
|
||||||
pub fn add_toggle(&mut self, toggle_value: usize, toggle_title: &'a str) {
|
pub fn add_toggle(&mut self, toggle_value: u32, toggle_title: &'a str, checked: bool) {
|
||||||
self.toggles.push(Toggle {
|
self.toggles.push(Toggle {
|
||||||
toggle_value: toggle_value,
|
toggle_value,
|
||||||
toggle_title: toggle_title,
|
toggle_title,
|
||||||
checked: false,
|
checked,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
pub fn new_with_toggles<T: ToggleTrait>(
|
pub fn new_with_toggles<T: ToggleTrait>(
|
||||||
|
@ -1223,6 +1227,7 @@ impl<'a> SubMenu<'a> {
|
||||||
submenu_id: &'a str,
|
submenu_id: &'a str,
|
||||||
help_text: &'a str,
|
help_text: &'a str,
|
||||||
is_single_option: bool,
|
is_single_option: bool,
|
||||||
|
initial_value: &u32
|
||||||
) -> SubMenu<'a> {
|
) -> SubMenu<'a> {
|
||||||
let mut instance = SubMenu {
|
let mut instance = SubMenu {
|
||||||
submenu_title: submenu_title,
|
submenu_title: submenu_title,
|
||||||
|
@ -1230,19 +1235,45 @@ impl<'a> SubMenu<'a> {
|
||||||
help_text: help_text,
|
help_text: help_text,
|
||||||
is_single_option: is_single_option,
|
is_single_option: is_single_option,
|
||||||
toggles: Vec::new(),
|
toggles: Vec::new(),
|
||||||
|
slider: None,
|
||||||
_type: "toggle",
|
_type: "toggle",
|
||||||
};
|
};
|
||||||
|
|
||||||
let values = T::to_toggle_vals();
|
let values = T::to_toggle_vals();
|
||||||
let titles = T::to_toggle_strs();
|
let titles = T::to_toggle_strs();
|
||||||
for i in 0..values.len() {
|
for i in 0..values.len() {
|
||||||
instance.add_toggle(values[i], titles[i]);
|
let checked: bool = (values[i] & initial_value) > 0
|
||||||
|
|| (!values[i] == 0 && initial_value == &0);
|
||||||
|
instance.add_toggle(values[i], titles[i], checked);
|
||||||
}
|
}
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
pub fn new_with_slider<S: SliderTrait>(
|
||||||
|
submenu_title: &'a str,
|
||||||
|
submenu_id: &'a str,
|
||||||
|
help_text: &'a str,
|
||||||
|
initial_lower_value: &u32,
|
||||||
|
initial_upper_value: &u32,
|
||||||
|
) -> SubMenu<'a> {
|
||||||
|
let min_max = S::get_limits();
|
||||||
|
SubMenu {
|
||||||
|
submenu_title: submenu_title,
|
||||||
|
submenu_id: submenu_id,
|
||||||
|
help_text: help_text,
|
||||||
|
is_single_option: false,
|
||||||
|
toggles: Vec::new(),
|
||||||
|
slider: Some(Slider {
|
||||||
|
selected_min: *initial_lower_value,
|
||||||
|
selected_max: *initial_upper_value,
|
||||||
|
abs_min: min_max.0,
|
||||||
|
abs_max: min_max.1,
|
||||||
|
}),
|
||||||
|
_type: "slider",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content)]
|
#[derive(Content, Serialize)]
|
||||||
pub struct Tab<'a> {
|
pub struct Tab<'a> {
|
||||||
pub tab_id: &'a str,
|
pub tab_id: &'a str,
|
||||||
pub tab_title: &'a str,
|
pub tab_title: &'a str,
|
||||||
|
@ -1256,17 +1287,36 @@ impl<'a> Tab<'a> {
|
||||||
submenu_id: &'a str,
|
submenu_id: &'a str,
|
||||||
help_text: &'a str,
|
help_text: &'a str,
|
||||||
is_single_option: bool,
|
is_single_option: bool,
|
||||||
|
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,
|
||||||
submenu_id,
|
submenu_id,
|
||||||
help_text,
|
help_text,
|
||||||
is_single_option,
|
is_single_option,
|
||||||
|
initial_value,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_submenu_with_slider<S: SliderTrait>(
|
||||||
|
&mut self,
|
||||||
|
submenu_title: &'a str,
|
||||||
|
submenu_id: &'a str,
|
||||||
|
help_text: &'a str,
|
||||||
|
initial_lower_value: &u32,
|
||||||
|
initial_upper_value: &u32,
|
||||||
|
) {
|
||||||
|
self.tab_submenus.push(SubMenu::new_with_slider::<S>(
|
||||||
|
submenu_title,
|
||||||
|
submenu_id,
|
||||||
|
help_text,
|
||||||
|
initial_lower_value,
|
||||||
|
initial_upper_value,
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content)]
|
#[derive(Content, Serialize)]
|
||||||
pub struct UiMenu<'a> {
|
pub struct UiMenu<'a> {
|
||||||
pub tabs: Vec<Tab<'a>>,
|
pub tabs: Vec<Tab<'a>>,
|
||||||
}
|
}
|
||||||
|
@ -1284,84 +1334,98 @@ pub unsafe fn get_menu() -> UiMenu<'static> {
|
||||||
"mash_state",
|
"mash_state",
|
||||||
"Mash Toggles: Actions to be performed as soon as possible",
|
"Mash Toggles: Actions to be performed as soon as possible",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.mash_state.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<Action>(
|
mash_tab.add_submenu_with_toggles::<Action>(
|
||||||
"Followup Toggles",
|
"Followup Toggles",
|
||||||
"follow_up",
|
"follow_up",
|
||||||
"Followup Toggles: Actions to be performed after the Mash option",
|
"Followup Toggles: Actions to be performed after the Mash option",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.follow_up.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<MashTrigger>(
|
mash_tab.add_submenu_with_toggles::<MashTrigger>(
|
||||||
"Mash Triggers",
|
"Mash Triggers",
|
||||||
"mash_triggers",
|
"mash_triggers",
|
||||||
"Mash triggers: When the Mash Option will be performed",
|
"Mash triggers: When the Mash Option will be performed",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.mash_triggers.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<AttackAngle>(
|
mash_tab.add_submenu_with_toggles::<AttackAngle>(
|
||||||
"Attack Angle",
|
"Attack Angle",
|
||||||
"attack_angle",
|
"attack_angle",
|
||||||
"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",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.attack_angle.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<ThrowOption>(
|
mash_tab.add_submenu_with_toggles::<ThrowOption>(
|
||||||
"Throw Options",
|
"Throw Options",
|
||||||
"throw_state",
|
"throw_state",
|
||||||
"Throw Options: Throw to be performed when a grab is landed",
|
"Throw Options: Throw to be performed when a grab is landed",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.throw_state.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<MedDelay>(
|
mash_tab.add_submenu_with_toggles::<MedDelay>(
|
||||||
"Throw Delay",
|
"Throw Delay",
|
||||||
"throw_delay",
|
"throw_delay",
|
||||||
"Throw Delay: How many frames to delay the throw option",
|
"Throw Delay: How many frames to delay the throw option",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.throw_delay.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<MedDelay>(
|
mash_tab.add_submenu_with_toggles::<MedDelay>(
|
||||||
"Pummel Delay",
|
"Pummel Delay",
|
||||||
"pummel_delay",
|
"pummel_delay",
|
||||||
"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",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.pummel_delay.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<BoolFlag>(
|
mash_tab.add_submenu_with_toggles::<BoolFlag>(
|
||||||
"Falling Aerials",
|
"Falling Aerials",
|
||||||
"falling_aerials",
|
"falling_aerials",
|
||||||
"Falling Aerials: Should aerials be performed when rising or when falling",
|
"Falling Aerials: Should aerials be performed when rising or when falling",
|
||||||
false, // TODO: Should this be a single option submenu?
|
false,
|
||||||
|
&(MENU.falling_aerials.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<BoolFlag>(
|
mash_tab.add_submenu_with_toggles::<BoolFlag>(
|
||||||
"Full Hop",
|
"Full Hop",
|
||||||
"full_hop",
|
"full_hop",
|
||||||
"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",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.full_hop.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<Delay>(
|
mash_tab.add_submenu_with_toggles::<Delay>(
|
||||||
"Aerial Delay",
|
"Aerial Delay",
|
||||||
"aerial_delay",
|
"aerial_delay",
|
||||||
"Aerial Delay: How long to delay a Mash aerial attack",
|
"Aerial Delay: How long to delay a Mash aerial attack",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.aerial_delay.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<BoolFlag>(
|
mash_tab.add_submenu_with_toggles::<BoolFlag>(
|
||||||
"Fast Fall",
|
"Fast Fall",
|
||||||
"fast_fall",
|
"fast_fall",
|
||||||
"Fast Fall: Should the CPU fastfall during a jump",
|
"Fast Fall: Should the CPU fastfall during a jump",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.fast_fall.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<Delay>(
|
mash_tab.add_submenu_with_toggles::<Delay>(
|
||||||
"Fast Fall Delay",
|
"Fast Fall Delay",
|
||||||
"fast_fall_delay",
|
"fast_fall_delay",
|
||||||
"Fast Fall Delay: How many frames the CPU should delay their fastfall",
|
"Fast Fall Delay: How many frames the CPU should delay their fastfall",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.fast_fall_delay.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<Delay>(
|
mash_tab.add_submenu_with_toggles::<Delay>(
|
||||||
"OoS Offset",
|
"OoS Offset",
|
||||||
"oos_offset",
|
"oos_offset",
|
||||||
"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",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.oos_offset.bits as u32),
|
||||||
);
|
);
|
||||||
mash_tab.add_submenu_with_toggles::<Delay>(
|
mash_tab.add_submenu_with_toggles::<Delay>(
|
||||||
"Reaction Time",
|
"Reaction Time",
|
||||||
"reaction_time",
|
"reaction_time",
|
||||||
"Reaction Time: How many frames to delay before performing a mash option",
|
"Reaction Time: How many frames to delay before performing a mash option",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.reaction_time.bits as u32),
|
||||||
);
|
);
|
||||||
overall_menu.tabs.push(mash_tab);
|
overall_menu.tabs.push(mash_tab);
|
||||||
|
|
||||||
|
@ -1375,178 +1439,199 @@ pub unsafe fn get_menu() -> UiMenu<'static> {
|
||||||
"air_dodge_dir",
|
"air_dodge_dir",
|
||||||
"Airdodge Direction: Direction to angle airdodges",
|
"Airdodge Direction: Direction to angle airdodges",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.air_dodge_dir.bits as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<Direction>(
|
defensive_tab.add_submenu_with_toggles::<Direction>(
|
||||||
"DI Direction",
|
"DI Direction",
|
||||||
"di_state",
|
"di_state",
|
||||||
"DI Direction: Direction to angle the directional influence during hitlag",
|
"DI Direction: Direction to angle the directional influence during hitlag",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.di_state.bits as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<Direction>(
|
defensive_tab.add_submenu_with_toggles::<Direction>(
|
||||||
"SDI Direction",
|
"SDI Direction",
|
||||||
"sdi_state",
|
"sdi_state",
|
||||||
"SDI Direction: Direction to angle the smash directional influence during hitlag",
|
"SDI Direction: Direction to angle the smash directional influence during hitlag",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.sdi_state.bits as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<InputFrequency>(
|
defensive_tab.add_submenu_with_toggles::<InputFrequency>(
|
||||||
"SDI Strength",
|
"SDI Strength",
|
||||||
"sdi_strength",
|
"sdi_strength",
|
||||||
"SDI Strength: Relative strength of the smash directional influence inputs",
|
"SDI Strength: Relative strength of the smash directional influence inputs",
|
||||||
true,
|
true,
|
||||||
|
&(MENU.sdi_strength as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<InputFrequency>(
|
defensive_tab.add_submenu_with_toggles::<InputFrequency>(
|
||||||
"Clatter Strength",
|
"Clatter Strength",
|
||||||
"clatter_strength",
|
"clatter_strength",
|
||||||
"Clatter Strength: Relative strength of the mashing out of grabs, buries, etc.",
|
"Clatter Strength: Relative strength of the mashing out of grabs, buries, etc.",
|
||||||
true,
|
true,
|
||||||
|
&(MENU.clatter_strength as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<LedgeOption>(
|
defensive_tab.add_submenu_with_toggles::<LedgeOption>(
|
||||||
"Ledge Options",
|
"Ledge Options",
|
||||||
"ledge_state",
|
"ledge_state",
|
||||||
"Ledge Options: Actions to be taken when on the ledge",
|
"Ledge Options: Actions to be taken when on the ledge",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.ledge_state.bits as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<LongDelay>(
|
defensive_tab.add_submenu_with_toggles::<LongDelay>(
|
||||||
"Ledge Delay",
|
"Ledge Delay",
|
||||||
"ledge_delay",
|
"ledge_delay",
|
||||||
"Ledge Delay: How many frames to delay the ledge option",
|
"Ledge Delay: How many frames to delay the ledge option",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.ledge_delay.bits as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<TechFlags>(
|
defensive_tab.add_submenu_with_toggles::<TechFlags>(
|
||||||
"Tech Options",
|
"Tech Options",
|
||||||
"tech_state",
|
"tech_state",
|
||||||
"Tech Options: Actions to take when slammed into a hard surface",
|
"Tech Options: Actions to take when slammed into a hard surface",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.tech_state.bits as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<MissTechFlags>(
|
defensive_tab.add_submenu_with_toggles::<MissTechFlags>(
|
||||||
"Mistech Options",
|
"Mistech Options",
|
||||||
"miss_tech_state",
|
"miss_tech_state",
|
||||||
"Mistech Options: Actions to take after missing a tech",
|
"Mistech Options: Actions to take after missing a tech",
|
||||||
false,
|
false,
|
||||||
|
&(MENU.miss_tech_state.bits as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<Shield>(
|
defensive_tab.add_submenu_with_toggles::<Shield>(
|
||||||
"Shield Toggles",
|
"Shield Toggles",
|
||||||
"shield_state",
|
"shield_state",
|
||||||
"Shield Toggles: CPU Shield Behavior",
|
"Shield Toggles: CPU Shield Behavior",
|
||||||
true,
|
true,
|
||||||
|
&(MENU.shield_state as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<Direction>(
|
defensive_tab.add_submenu_with_toggles::<Direction>(
|
||||||
"Shield Tilt",
|
"Shield Tilt",
|
||||||
"shield_tilt",
|
"shield_tilt",
|
||||||
"Shield Tilt: Direction to tilt the shield",
|
"Shield Tilt: Direction to tilt the shield",
|
||||||
false, // TODO: Should this be true?
|
false, // TODO: Should this be true?
|
||||||
|
&(MENU.shield_tilt.bits as u32),
|
||||||
);
|
);
|
||||||
defensive_tab.add_submenu_with_toggles::<BuffOption>(
|
|
||||||
"Buff Options",
|
|
||||||
"buff_state",
|
|
||||||
"Buff Options: Buff(s) to be applied to respective character when loading save states",
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
defensive_tab.add_submenu_with_toggles::<CharacterItem>(
|
|
||||||
"Character Item",
|
|
||||||
"character_item",
|
|
||||||
"Character Item: CPU/Player item to hold when loading a save state",
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
defensive_tab.add_submenu_with_toggles::<OnOff>(
|
defensive_tab.add_submenu_with_toggles::<OnOff>(
|
||||||
"Crouch",
|
"Crouch",
|
||||||
"crouch",
|
"crouch",
|
||||||
"Crouch: Should the CPU crouch when on the ground",
|
"Crouch: Should the CPU crouch when on the ground",
|
||||||
true,
|
true,
|
||||||
|
&(MENU.crouch as u32),
|
||||||
);
|
);
|
||||||
overall_menu.tabs.push(defensive_tab);
|
overall_menu.tabs.push(defensive_tab);
|
||||||
|
|
||||||
|
let mut save_state_tab = Tab {
|
||||||
|
tab_id: "save_state",
|
||||||
|
tab_title: "Save States",
|
||||||
|
tab_submenus: Vec::new(),
|
||||||
|
};
|
||||||
|
save_state_tab.add_submenu_with_toggles::<SaveStateMirroring>(
|
||||||
|
"Mirroring",
|
||||||
|
"save_state_mirroring",
|
||||||
|
"Mirroring: Flips save states in the left-right direction across the stage center",
|
||||||
|
true,
|
||||||
|
&(MENU.save_state_mirroring as u32),
|
||||||
|
);
|
||||||
|
save_state_tab.add_submenu_with_toggles::<OnOff>(
|
||||||
|
"Auto Save States",
|
||||||
|
"save_state_autoload",
|
||||||
|
"Auto Save States: Load save state when any fighter dies",
|
||||||
|
true,
|
||||||
|
&(MENU.save_state_autoload as u32),
|
||||||
|
);
|
||||||
|
save_state_tab.add_submenu_with_toggles::<SaveDamage>(
|
||||||
|
"Save Dmg (CPU)",
|
||||||
|
"save_damage_cpu",
|
||||||
|
"Save Damage: Should save states retain CPU damage",
|
||||||
|
true,
|
||||||
|
&(MENU.save_damage_cpu.bits as u32),
|
||||||
|
);
|
||||||
|
save_state_tab.add_submenu_with_slider::<DamagePercent>(
|
||||||
|
"Dmg Range (CPU)",
|
||||||
|
"save_damage_limits_cpu",
|
||||||
|
"Limits on random damage to apply to the CPU when loading a save state",
|
||||||
|
&(MENU.save_damage_limits_cpu.0 as u32),
|
||||||
|
&(MENU.save_damage_limits_cpu.1 as u32),
|
||||||
|
);
|
||||||
|
save_state_tab.add_submenu_with_toggles::<SaveDamage>(
|
||||||
|
"Save Dmg (Player)",
|
||||||
|
"save_damage_player",
|
||||||
|
"Save Damage: Should save states retain player damage",
|
||||||
|
true,
|
||||||
|
&(MENU.save_damage_player.bits as u32),
|
||||||
|
);
|
||||||
|
save_state_tab.add_submenu_with_slider::<DamagePercent>(
|
||||||
|
"Dmg Range (Player)",
|
||||||
|
"save_damage_limits_player",
|
||||||
|
"Limits on random damage to apply to the player when loading a save state",
|
||||||
|
&(MENU.save_damage_limits_player.0 as u32),
|
||||||
|
&(MENU.save_damage_limits_player.1 as u32),
|
||||||
|
);
|
||||||
|
save_state_tab.add_submenu_with_toggles::<OnOff>(
|
||||||
|
"Enable Save States",
|
||||||
|
"save_state_enable",
|
||||||
|
"Save States: Enable save states! Save a state with Grab+Down Taunt, load it with Grab+Up Taunt.",
|
||||||
|
true,
|
||||||
|
&(MENU.save_state_enable as u32),
|
||||||
|
);
|
||||||
|
save_state_tab.add_submenu_with_toggles::<CharacterItem>(
|
||||||
|
"Character Item",
|
||||||
|
"character_item",
|
||||||
|
"Character Item: CPU/Player item to hold when loading a save state",
|
||||||
|
true,
|
||||||
|
&(MENU.character_item as u32),
|
||||||
|
);
|
||||||
|
save_state_tab.add_submenu_with_toggles::<BuffOption>(
|
||||||
|
"Buff Options",
|
||||||
|
"buff_state",
|
||||||
|
"Buff Options: Buff(s) to be applied to respective character when loading save states",
|
||||||
|
false,
|
||||||
|
&(MENU.buff_state.bits as u32),
|
||||||
|
);
|
||||||
|
overall_menu.tabs.push(save_state_tab);
|
||||||
|
|
||||||
let mut misc_tab = Tab {
|
let mut misc_tab = Tab {
|
||||||
tab_id: "misc",
|
tab_id: "misc",
|
||||||
tab_title: "Misc Settings",
|
tab_title: "Misc Settings",
|
||||||
tab_submenus: Vec::new(),
|
tab_submenus: Vec::new(),
|
||||||
};
|
};
|
||||||
misc_tab.add_submenu_with_toggles::<SaveStateMirroring>(
|
|
||||||
"Mirroring",
|
|
||||||
"save_state_mirroring",
|
|
||||||
"Mirroring: Flips save states in the left-right direction across the stage center",
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
misc_tab.add_submenu_with_toggles::<OnOff>(
|
|
||||||
"Save Damage",
|
|
||||||
"save_damage",
|
|
||||||
"Save Damage: Should save states retain player/CPU damage",
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
misc_tab.add_submenu_with_toggles::<OnOff>(
|
|
||||||
"Enable Save States",
|
|
||||||
"save_state_enable",
|
|
||||||
"Save States: Enable save states! Save a state with Grab+Down Taunt, load it with Grab+Up Taunt.",
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
misc_tab.add_submenu_with_toggles::<OnOff>(
|
|
||||||
"Save States Autoload",
|
|
||||||
"save_state_autoload",
|
|
||||||
"Save States Autoload: Load save state when any fighter dies",
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
misc_tab.add_submenu_with_toggles::<OnOff>(
|
misc_tab.add_submenu_with_toggles::<OnOff>(
|
||||||
"Frame Advantage",
|
"Frame Advantage",
|
||||||
"frame_advantage",
|
"frame_advantage",
|
||||||
"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",
|
||||||
true,
|
true,
|
||||||
|
&(MENU.frame_advantage as u32),
|
||||||
);
|
);
|
||||||
misc_tab.add_submenu_with_toggles::<OnOff>(
|
misc_tab.add_submenu_with_toggles::<OnOff>(
|
||||||
"Hitbox Visualization",
|
"Hitbox Visualization",
|
||||||
"hitbox_vis",
|
"hitbox_vis",
|
||||||
"Hitbox Visualization: Should hitboxes be displayed, hiding other visual effects",
|
"Hitbox Visualization: Should hitboxes be displayed, hiding other visual effects",
|
||||||
true,
|
true,
|
||||||
|
&(MENU.hitbox_vis as u32),
|
||||||
);
|
);
|
||||||
misc_tab.add_submenu_with_toggles::<Delay>(
|
misc_tab.add_submenu_with_toggles::<Delay>(
|
||||||
"Input Delay",
|
"Input Delay",
|
||||||
"input_delay",
|
"input_delay",
|
||||||
"Input Delay: Frames to delay player inputs by",
|
"Input Delay: Frames to delay player inputs by",
|
||||||
true,
|
true,
|
||||||
|
&(MENU.input_delay.bits as u32),
|
||||||
);
|
);
|
||||||
misc_tab.add_submenu_with_toggles::<OnOff>(
|
misc_tab.add_submenu_with_toggles::<OnOff>(
|
||||||
"Stage Hazards",
|
"Stage Hazards",
|
||||||
"stage_hazards",
|
"stage_hazards",
|
||||||
"Stage Hazards: Should stage hazards be present",
|
"Stage Hazards: Should stage hazards be present",
|
||||||
true,
|
true,
|
||||||
|
&(MENU.stage_hazards as u32),
|
||||||
);
|
);
|
||||||
misc_tab.add_submenu_with_toggles::<OnOff>(
|
misc_tab.add_submenu_with_toggles::<OnOff>(
|
||||||
"Quick Menu",
|
"Quick Menu",
|
||||||
"quick_menu",
|
"quick_menu",
|
||||||
"Quick Menu: Should use quick or web menu",
|
"Quick Menu: Should use quick or web menu",
|
||||||
true,
|
true,
|
||||||
|
&(MENU.quick_menu as u32),
|
||||||
);
|
);
|
||||||
overall_menu.tabs.push(misc_tab);
|
overall_menu.tabs.push(misc_tab);
|
||||||
|
|
||||||
let non_ui_menu = serde_json::to_string(&MENU)
|
|
||||||
.unwrap()
|
|
||||||
.replace("\"", "")
|
|
||||||
.replace("{", "")
|
|
||||||
.replace("}", "");
|
|
||||||
let toggle_values_all = non_ui_menu.split(',').collect::<Vec<&str>>();
|
|
||||||
let mut sub_menu_id_to_vals: HashMap<&str, u32> = HashMap::new();
|
|
||||||
for toggle_values in toggle_values_all {
|
|
||||||
let toggle_value_split = toggle_values.split(':').collect::<Vec<&str>>();
|
|
||||||
let sub_menu_id = toggle_value_split[0];
|
|
||||||
if sub_menu_id.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let full_bits: u32 = toggle_value_split[1].parse().unwrap_or(0);
|
|
||||||
sub_menu_id_to_vals.insert(&sub_menu_id, full_bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
overall_menu.tabs.iter_mut().for_each(|tab| {
|
|
||||||
tab.tab_submenus.iter_mut().for_each(|sub_menu| {
|
|
||||||
let sub_menu_id = sub_menu.submenu_id;
|
|
||||||
sub_menu.toggles.iter_mut().for_each(|toggle| {
|
|
||||||
if sub_menu_id_to_vals.contains_key(sub_menu_id)
|
|
||||||
&& (sub_menu_id_to_vals[sub_menu_id] & (toggle.toggle_value as u32) != 0)
|
|
||||||
{
|
|
||||||
toggle.checked = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
overall_menu
|
overall_menu
|
||||||
}
|
}
|
||||||
|
|
27
training_mod_tui/src/gauge.rs
Normal file
27
training_mod_tui/src/gauge.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
pub enum GaugeState {
|
||||||
|
MinHover,
|
||||||
|
MaxHover,
|
||||||
|
MinSelected,
|
||||||
|
MaxSelected,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DoubleEndedGauge {
|
||||||
|
pub state: GaugeState,
|
||||||
|
pub selected_min: u32,
|
||||||
|
pub selected_max: u32,
|
||||||
|
pub abs_min: u32,
|
||||||
|
pub abs_max: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DoubleEndedGauge {
|
||||||
|
pub fn new() -> DoubleEndedGauge {
|
||||||
|
DoubleEndedGauge {
|
||||||
|
state: GaugeState::None,
|
||||||
|
selected_min: 0,
|
||||||
|
selected_max: 150,
|
||||||
|
abs_min: 0,
|
||||||
|
abs_max: 150,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,24 @@
|
||||||
use training_mod_consts::{Slider, SubMenu, SubMenuType, Toggle, UiMenu};
|
use training_mod_consts::{Slider, SubMenu, SubMenuType, Toggle, UiMenu};
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::{Backend},
|
backend::Backend,
|
||||||
layout::{Constraint, Corner, Direction, Layout},
|
layout::{Constraint, Corner, Direction, Layout, Rect},
|
||||||
style::{Modifier, Style},
|
style::{Modifier, Style},
|
||||||
text::{Span, Spans},
|
text::{Span, Spans},
|
||||||
widgets::{Tabs, Paragraph, Block, List, ListItem, ListState},
|
widgets::{Block, LineGauge, List, ListItem, ListState, Paragraph, Tabs},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use tui::{backend::TestBackend, Terminal, style::Color};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use serde_json::{Map, json};
|
||||||
|
pub use tui::{backend::TestBackend, style::Color, Terminal};
|
||||||
|
|
||||||
|
mod gauge;
|
||||||
mod list;
|
mod list;
|
||||||
|
|
||||||
use crate::list::{StatefulList, MultiStatefulList};
|
use crate::gauge::{DoubleEndedGauge, GaugeState};
|
||||||
|
use crate::list::{MultiStatefulList, StatefulList};
|
||||||
|
|
||||||
|
static NX_TUI_WIDTH: u16 = 66;
|
||||||
|
|
||||||
/// 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.
|
||||||
|
@ -22,8 +27,8 @@ pub struct App<'a> {
|
||||||
pub tabs: StatefulList<&'a str>,
|
pub tabs: StatefulList<&'a str>,
|
||||||
pub menu_items: HashMap<&'a str, MultiStatefulList<SubMenu<'a>>>,
|
pub menu_items: HashMap<&'a str, MultiStatefulList<SubMenu<'a>>>,
|
||||||
pub selected_sub_menu_toggles: MultiStatefulList<Toggle<'a>>,
|
pub selected_sub_menu_toggles: MultiStatefulList<Toggle<'a>>,
|
||||||
pub selected_sub_menu_sliders: MultiStatefulList<Slider>,
|
pub selected_sub_menu_slider: DoubleEndedGauge,
|
||||||
pub outer_list: bool
|
pub outer_list: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> App<'a> {
|
impl<'a> App<'a> {
|
||||||
|
@ -34,7 +39,7 @@ impl<'a> App<'a> {
|
||||||
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,
|
||||||
MultiStatefulList::with_items(tab.tab_submenus.clone(), num_lists)
|
MultiStatefulList::with_items(tab.tab_submenus.clone(), num_lists),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -42,121 +47,286 @@ impl<'a> App<'a> {
|
||||||
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).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_sliders: MultiStatefulList::with_items(vec![], 0),
|
selected_sub_menu_slider: DoubleEndedGauge::new(),
|
||||||
outer_list: true
|
outer_list: true,
|
||||||
};
|
};
|
||||||
app.set_sub_menu_items();
|
app.set_sub_menu_items();
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes the currently selected tab/submenu and clones the options into
|
||||||
|
/// self.selected_sub_menu_toggles and self.selected_sub_menu_slider
|
||||||
pub fn set_sub_menu_items(&mut self) {
|
pub fn set_sub_menu_items(&mut self) {
|
||||||
let (list_section, list_idx) = self.menu_items.get(self.tab_selected()).unwrap().idx_to_list_idx(self.menu_items.get(self.tab_selected()).unwrap().state);
|
let (list_section, list_idx) = self
|
||||||
let selected_sub_menu = &self.menu_items.get(self.tab_selected()).unwrap().lists[list_section].items.get(list_idx).unwrap();
|
.menu_items
|
||||||
|
.get(self.tab_selected())
|
||||||
|
.unwrap()
|
||||||
|
.idx_to_list_idx(self.menu_items.get(self.tab_selected()).unwrap().state);
|
||||||
|
let selected_sub_menu = &self.menu_items.get(self.tab_selected()).unwrap().lists
|
||||||
|
[list_section]
|
||||||
|
.items
|
||||||
|
.get(list_idx)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let toggles = selected_sub_menu.toggles.clone();
|
let toggles = selected_sub_menu.toggles.clone();
|
||||||
// let sliders = selected_sub_menu.sliders.clone();
|
let slider = selected_sub_menu.slider.clone();
|
||||||
match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
match SubMenuType::from_str(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,
|
||||||
if selected_sub_menu.toggles.len() >= 3 { 3 } else { selected_sub_menu.toggles.len()} )
|
if selected_sub_menu.toggles.len() >= 3 {
|
||||||
},
|
3
|
||||||
|
} else {
|
||||||
|
selected_sub_menu.toggles.len()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
SubMenuType::SLIDER => {
|
SubMenuType::SLIDER => {
|
||||||
// self.selected_sub_menu_sliders = MultiStatefulList::with_items(
|
let slider = slider.unwrap();
|
||||||
// sliders,
|
self.selected_sub_menu_slider = DoubleEndedGauge {
|
||||||
// if selected_sub_menu.sliders.len() >= 3 { 3 } else { selected_sub_menu.sliders.len()} )
|
state: GaugeState::None,
|
||||||
},
|
selected_min: slider.selected_min,
|
||||||
|
selected_max: slider.selected_max,
|
||||||
|
abs_min: slider.abs_min,
|
||||||
|
abs_max: slider.abs_max,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the id of the currently selected tab
|
||||||
fn tab_selected(&self) -> &str {
|
fn tab_selected(&self) -> &str {
|
||||||
self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()
|
self.tabs
|
||||||
|
.items
|
||||||
|
.get(self.tabs.state.selected().unwrap())
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the currently selected SubMenu struct
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// submenu_title: &'a str,
|
||||||
|
/// submenu_id: &'a str,
|
||||||
|
/// help_text: &'a str,
|
||||||
|
/// is_single_option: bool,
|
||||||
|
/// toggles: Vec<Toggle<'a>>,
|
||||||
|
/// slider: Option<Slider>,
|
||||||
|
/// _type: &'a str,
|
||||||
|
/// }
|
||||||
fn sub_menu_selected(&self) -> &SubMenu {
|
fn sub_menu_selected(&self) -> &SubMenu {
|
||||||
let (list_section, list_idx) = self.menu_items.get(self.tab_selected()).unwrap().idx_to_list_idx(self.menu_items.get(self.tab_selected()).unwrap().state);
|
let (list_section, list_idx) = self
|
||||||
self.menu_items.get(self.tab_selected()).unwrap().lists[list_section].items.get(list_idx).unwrap()
|
.menu_items
|
||||||
|
.get(self.tab_selected())
|
||||||
|
.unwrap()
|
||||||
|
.idx_to_list_idx(self.menu_items.get(self.tab_selected()).unwrap().state);
|
||||||
|
self.menu_items.get(self.tab_selected()).unwrap().lists[list_section]
|
||||||
|
.items
|
||||||
|
.get(list_idx)
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A "next()" function which differs per submenu type
|
||||||
|
/// Toggles: calls next()
|
||||||
|
/// 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_str(self.sub_menu_selected()._type) {
|
||||||
SubMenuType::TOGGLE => self.selected_sub_menu_toggles.next(),
|
SubMenuType::TOGGLE => self.selected_sub_menu_toggles.next(),
|
||||||
SubMenuType::SLIDER => self.selected_sub_menu_sliders.next(),
|
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
|
||||||
|
GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover,
|
||||||
|
GaugeState::MaxHover => self.selected_sub_menu_slider.state = GaugeState::MinHover,
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A "next_list()" function which differs per submenu type
|
||||||
|
/// Toggles: Calls next_list()
|
||||||
|
/// Slider:
|
||||||
|
/// * Swaps between MinHover and MaxHover
|
||||||
|
/// * 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_str(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 => self.selected_sub_menu_sliders.next_list(),
|
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
|
||||||
|
GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover,
|
||||||
|
GaugeState::MaxHover => self.selected_sub_menu_slider.state = GaugeState::MinHover,
|
||||||
|
GaugeState::MinSelected => {
|
||||||
|
if self.selected_sub_menu_slider.selected_min
|
||||||
|
< self.selected_sub_menu_slider.selected_max
|
||||||
|
{
|
||||||
|
self.selected_sub_menu_slider.selected_min += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GaugeState::MaxSelected => {
|
||||||
|
if self.selected_sub_menu_slider.selected_max
|
||||||
|
< self.selected_sub_menu_slider.abs_max
|
||||||
|
{
|
||||||
|
self.selected_sub_menu_slider.selected_max += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GaugeState::None => {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A "previous()" function which differs per submenu type
|
||||||
|
/// Toggles: calls previous()
|
||||||
|
/// 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_str(self.sub_menu_selected()._type) {
|
||||||
SubMenuType::TOGGLE => self.selected_sub_menu_toggles.previous(),
|
SubMenuType::TOGGLE => self.selected_sub_menu_toggles.previous(),
|
||||||
SubMenuType::SLIDER => self.selected_sub_menu_sliders.previous(),
|
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
|
||||||
|
GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover,
|
||||||
|
GaugeState::MaxHover => self.selected_sub_menu_slider.state = GaugeState::MinHover,
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A "previous_list()" function which differs per submenu type
|
||||||
|
/// Toggles: Calls previous_list()
|
||||||
|
/// Slider:
|
||||||
|
/// * Swaps between MinHover and MaxHover
|
||||||
|
/// * 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_str(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 => self.selected_sub_menu_sliders.previous_list(),
|
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
|
||||||
|
GaugeState::MinHover => self.selected_sub_menu_slider.state = GaugeState::MaxHover,
|
||||||
|
GaugeState::MaxHover => self.selected_sub_menu_slider.state = GaugeState::MinHover,
|
||||||
|
GaugeState::MinSelected => {
|
||||||
|
if self.selected_sub_menu_slider.selected_min
|
||||||
|
> self.selected_sub_menu_slider.abs_min
|
||||||
|
{
|
||||||
|
self.selected_sub_menu_slider.selected_min -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GaugeState::MaxSelected => {
|
||||||
|
if self.selected_sub_menu_slider.selected_max
|
||||||
|
> self.selected_sub_menu_slider.selected_min
|
||||||
|
{
|
||||||
|
self.selected_sub_menu_slider.selected_max -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GaugeState::None => {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sub_menu_strs_and_states(&mut self) -> (&str, &str, Vec<(Vec<(bool, &str)>, ListState)>) {
|
/// Returns information about the currently selected submenu
|
||||||
(self.sub_menu_selected().submenu_title, self.sub_menu_selected().help_text,
|
///
|
||||||
match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
/// 0: Submenu Title
|
||||||
SubMenuType::TOGGLE => {
|
/// 1: Submenu Help Text
|
||||||
self.selected_sub_menu_toggles.lists.iter().map(|toggle_list| {
|
/// 2: Vec(toggle checked, title) for toggles, Vec(nothing) for slider
|
||||||
(toggle_list.items.iter().map(
|
/// 3: ListState for toggles, ListState::new() for slider
|
||||||
|toggle| (toggle.checked, toggle.toggle_title)
|
/// TODO: Refactor return type into a nice struct
|
||||||
).collect(), toggle_list.state.clone())
|
pub fn sub_menu_strs_and_states(
|
||||||
}).collect()
|
&mut self,
|
||||||
|
) -> (&str, &str, Vec<(Vec<(bool, &str)>, ListState)>) {
|
||||||
|
(
|
||||||
|
self.sub_menu_selected().submenu_title,
|
||||||
|
self.sub_menu_selected().help_text,
|
||||||
|
match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
||||||
|
SubMenuType::TOGGLE => self
|
||||||
|
.selected_sub_menu_toggles
|
||||||
|
.lists
|
||||||
|
.iter()
|
||||||
|
.map(|toggle_list| {
|
||||||
|
(
|
||||||
|
toggle_list
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.map(|toggle| (toggle.checked, toggle.toggle_title))
|
||||||
|
.collect(),
|
||||||
|
toggle_list.state.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
SubMenuType::SLIDER => {
|
||||||
|
vec![(vec![], ListState::default())]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
SubMenuType::SLIDER => {
|
)
|
||||||
vec![(vec![], ListState::default())]
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns information about the currently selected slider
|
||||||
|
/// 0: Title
|
||||||
|
/// 1: Help text
|
||||||
|
/// 2: Reference to self.selected_sub_menu_slider
|
||||||
|
/// TODO: Refactor return type into a nice struct
|
||||||
|
pub fn sub_menu_strs_for_slider(&mut self) -> (&str, &str, &DoubleEndedGauge) {
|
||||||
|
let slider = match SubMenuType::from_str(self.sub_menu_selected()._type) {
|
||||||
|
SubMenuType::SLIDER => &self.selected_sub_menu_slider,
|
||||||
|
_ => {
|
||||||
|
panic!("Slider not selected!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(
|
||||||
|
self.sub_menu_selected().submenu_title,
|
||||||
|
self.sub_menu_selected().help_text,
|
||||||
|
slider,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Different behavior depending on the current menu location
|
||||||
|
/// Outer list: Sets self.outer_list to false
|
||||||
|
/// Toggle submenu: Toggles the selected submenu toggle in self.selected_sub_menu_toggles and in the actual SubMenu struct
|
||||||
|
/// Slider submenu: Swaps hover/selected state. Updates the actual SubMenu struct if going from Selected -> Hover
|
||||||
pub fn on_a(&mut self) {
|
pub fn on_a(&mut self) {
|
||||||
|
let tab_selected = self
|
||||||
|
.tabs
|
||||||
|
.items
|
||||||
|
.get(self.tabs.state.selected().unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let (list_section, list_idx) = self
|
||||||
|
.menu_items
|
||||||
|
.get(tab_selected)
|
||||||
|
.unwrap()
|
||||||
|
.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
|
||||||
|
[list_section]
|
||||||
|
.items
|
||||||
|
.get_mut(list_idx)
|
||||||
|
.unwrap();
|
||||||
if self.outer_list {
|
if self.outer_list {
|
||||||
self.outer_list = false;
|
self.outer_list = false;
|
||||||
|
match SubMenuType::from_str(selected_sub_menu._type) {
|
||||||
|
// Need to change the slider state to MinHover so the slider shows up initially
|
||||||
|
SubMenuType::SLIDER => {
|
||||||
|
self.selected_sub_menu_slider.state = GaugeState::MinHover;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let tab_selected = self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap();
|
|
||||||
let (list_section, list_idx) = self.menu_items.get(tab_selected)
|
|
||||||
.unwrap()
|
|
||||||
.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[list_section]
|
|
||||||
.items.get_mut(list_idx).unwrap();
|
|
||||||
match SubMenuType::from_str(selected_sub_menu._type) {
|
match SubMenuType::from_str(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;
|
||||||
self.selected_sub_menu_toggles.lists.iter_mut()
|
// Change the toggles in self.selected_sub_menu_toggles (for display)
|
||||||
|
self.selected_sub_menu_toggles
|
||||||
|
.lists
|
||||||
|
.iter_mut()
|
||||||
.map(|list| (list.state.selected(), &mut list.items))
|
.map(|list| (list.state.selected(), &mut list.items))
|
||||||
.for_each(|(state, toggle_list)| toggle_list.iter_mut()
|
.for_each(|(state, toggle_list)| {
|
||||||
.enumerate()
|
toggle_list.iter_mut().enumerate().for_each(|(i, o)| {
|
||||||
.for_each(|(i, o)|
|
if state.is_some() && i == state.unwrap() {
|
||||||
if state.is_some() && i == state.unwrap() {
|
if !o.checked {
|
||||||
if !o.checked {
|
o.checked = true;
|
||||||
o.checked = true;
|
} else {
|
||||||
} else {
|
o.checked = false;
|
||||||
o.checked = false;
|
}
|
||||||
}
|
} else if is_single_option {
|
||||||
} else if is_single_option {
|
o.checked = false;
|
||||||
o.checked = false;
|
}
|
||||||
}
|
})
|
||||||
));
|
});
|
||||||
selected_sub_menu.toggles.iter_mut()
|
// Actually change the toggle values in the SubMenu struct
|
||||||
|
selected_sub_menu
|
||||||
|
.toggles
|
||||||
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.for_each(|(i, o)| {
|
.for_each(|(i, o)| {
|
||||||
if i == state {
|
if i == state {
|
||||||
if !o.checked {
|
if !o.checked {
|
||||||
o.checked = true;
|
o.checked = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -166,16 +336,89 @@ impl<'a> App<'a> {
|
||||||
o.checked = false;
|
o.checked = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
SubMenuType::SLIDER => {
|
|
||||||
// self.selected_sub_menu_sliders.selected_list_item().checked = true;
|
|
||||||
}
|
}
|
||||||
|
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
|
||||||
|
GaugeState::MinHover => {
|
||||||
|
self.selected_sub_menu_slider.state = GaugeState::MinSelected;
|
||||||
|
}
|
||||||
|
GaugeState::MaxHover => {
|
||||||
|
self.selected_sub_menu_slider.state = GaugeState::MaxSelected;
|
||||||
|
}
|
||||||
|
GaugeState::MinSelected => {
|
||||||
|
self.selected_sub_menu_slider.state = GaugeState::MinHover;
|
||||||
|
selected_sub_menu.slider = Some(Slider{
|
||||||
|
selected_min: self.selected_sub_menu_slider.selected_min,
|
||||||
|
selected_max: self.selected_sub_menu_slider.selected_max,
|
||||||
|
abs_min: self.selected_sub_menu_slider.abs_min,
|
||||||
|
abs_max: self.selected_sub_menu_slider.abs_max,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
GaugeState::MaxSelected => {
|
||||||
|
self.selected_sub_menu_slider.state = GaugeState::MaxHover;
|
||||||
|
selected_sub_menu.slider = Some(Slider{
|
||||||
|
selected_min: self.selected_sub_menu_slider.selected_min,
|
||||||
|
selected_max: self.selected_sub_menu_slider.selected_max,
|
||||||
|
abs_min: self.selected_sub_menu_slider.abs_min,
|
||||||
|
abs_max: self.selected_sub_menu_slider.abs_max,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
GaugeState::None => {
|
||||||
|
self.selected_sub_menu_slider.state = GaugeState::MinHover;
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Different behavior depending on the current menu location
|
||||||
|
/// Outer list: None
|
||||||
|
/// Toggle submenu: Sets self.outer_list to true
|
||||||
|
/// Slider submenu: If in a selected state, then commit changes and change to hover. Else set self.outer_list to true
|
||||||
pub fn on_b(&mut self) {
|
pub fn on_b(&mut self) {
|
||||||
|
let tab_selected = self
|
||||||
|
.tabs
|
||||||
|
.items
|
||||||
|
.get(self.tabs.state.selected().unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let (list_section, list_idx) = self
|
||||||
|
.menu_items
|
||||||
|
.get(tab_selected)
|
||||||
|
.unwrap()
|
||||||
|
.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[list_section]
|
||||||
|
.items
|
||||||
|
.get_mut(list_idx)
|
||||||
|
.unwrap();
|
||||||
|
match SubMenuType::from_str(selected_sub_menu._type) {
|
||||||
|
SubMenuType::SLIDER => match self.selected_sub_menu_slider.state {
|
||||||
|
GaugeState::MinSelected => {
|
||||||
|
self.selected_sub_menu_slider.state = GaugeState::MinHover;
|
||||||
|
selected_sub_menu.slider = Some(Slider{
|
||||||
|
selected_min: self.selected_sub_menu_slider.selected_min,
|
||||||
|
selected_max: self.selected_sub_menu_slider.selected_max,
|
||||||
|
abs_min: self.selected_sub_menu_slider.abs_min,
|
||||||
|
abs_max: self.selected_sub_menu_slider.abs_max,
|
||||||
|
});
|
||||||
|
// Don't go back to the outer list
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GaugeState::MaxSelected => {
|
||||||
|
self.selected_sub_menu_slider.state = GaugeState::MaxHover;
|
||||||
|
selected_sub_menu.slider = Some(Slider{
|
||||||
|
selected_min: self.selected_sub_menu_slider.selected_min,
|
||||||
|
selected_max: self.selected_sub_menu_slider.selected_max,
|
||||||
|
abs_min: self.selected_sub_menu_slider.abs_min,
|
||||||
|
abs_max: self.selected_sub_menu_slider.abs_max,
|
||||||
|
});
|
||||||
|
// Don't go back to the outer list
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
self.outer_list = true;
|
self.outer_list = true;
|
||||||
|
self.set_sub_menu_items();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_l(&mut self) {
|
pub fn on_l(&mut self) {
|
||||||
|
@ -194,7 +437,15 @@ impl<'a> App<'a> {
|
||||||
|
|
||||||
pub fn on_up(&mut self) {
|
pub fn on_up(&mut self) {
|
||||||
if self.outer_list {
|
if self.outer_list {
|
||||||
self.menu_items.get_mut(self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()).unwrap().previous();
|
self.menu_items
|
||||||
|
.get_mut(
|
||||||
|
self.tabs
|
||||||
|
.items
|
||||||
|
.get(self.tabs.state.selected().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.previous();
|
||||||
self.set_sub_menu_items();
|
self.set_sub_menu_items();
|
||||||
} else {
|
} else {
|
||||||
self.sub_menu_previous();
|
self.sub_menu_previous();
|
||||||
|
@ -203,7 +454,15 @@ impl<'a> App<'a> {
|
||||||
|
|
||||||
pub fn on_down(&mut self) {
|
pub fn on_down(&mut self) {
|
||||||
if self.outer_list {
|
if self.outer_list {
|
||||||
self.menu_items.get_mut(self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()).unwrap().next();
|
self.menu_items
|
||||||
|
.get_mut(
|
||||||
|
self.tabs
|
||||||
|
.items
|
||||||
|
.get(self.tabs.state.selected().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.next();
|
||||||
self.set_sub_menu_items();
|
self.set_sub_menu_items();
|
||||||
} else {
|
} else {
|
||||||
self.sub_menu_next();
|
self.sub_menu_next();
|
||||||
|
@ -212,7 +471,15 @@ impl<'a> App<'a> {
|
||||||
|
|
||||||
pub fn on_left(&mut self) {
|
pub fn on_left(&mut self) {
|
||||||
if self.outer_list {
|
if self.outer_list {
|
||||||
self.menu_items.get_mut(self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()).unwrap().previous_list();
|
self.menu_items
|
||||||
|
.get_mut(
|
||||||
|
self.tabs
|
||||||
|
.items
|
||||||
|
.get(self.tabs.state.selected().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.previous_list();
|
||||||
self.set_sub_menu_items();
|
self.set_sub_menu_items();
|
||||||
} else {
|
} else {
|
||||||
self.sub_menu_previous_list();
|
self.sub_menu_previous_list();
|
||||||
|
@ -221,7 +488,15 @@ impl<'a> App<'a> {
|
||||||
|
|
||||||
pub fn on_right(&mut self) {
|
pub fn on_right(&mut self) {
|
||||||
if self.outer_list {
|
if self.outer_list {
|
||||||
self.menu_items.get_mut(self.tabs.items.get(self.tabs.state.selected().unwrap()).unwrap()).unwrap().next_list();
|
self.menu_items
|
||||||
|
.get_mut(
|
||||||
|
self.tabs
|
||||||
|
.items
|
||||||
|
.get(self.tabs.state.selected().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.next_list();
|
||||||
self.set_sub_menu_items();
|
self.set_sub_menu_items();
|
||||||
} else {
|
} else {
|
||||||
self.sub_menu_next_list();
|
self.sub_menu_next_list();
|
||||||
|
@ -232,36 +507,106 @@ impl<'a> App<'a> {
|
||||||
pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
|
pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
|
||||||
let app_tabs = &app.tabs;
|
let app_tabs = &app.tabs;
|
||||||
let tab_selected = app_tabs.state.selected().unwrap();
|
let tab_selected = app_tabs.state.selected().unwrap();
|
||||||
let titles = app_tabs.items.iter().cloned().enumerate().map(|(idx, tab)|{
|
let mut span_selected = Spans::default();
|
||||||
if idx == tab_selected {
|
|
||||||
Spans::from(">> ".to_owned() + tab)
|
let titles: Vec<Spans> = app_tabs
|
||||||
} else {
|
.items
|
||||||
Spans::from(" ".to_owned() + tab)
|
.iter()
|
||||||
}
|
.cloned()
|
||||||
}).collect();
|
.enumerate()
|
||||||
|
.map(|(idx, tab)| {
|
||||||
|
if idx == tab_selected {
|
||||||
|
span_selected = Spans::from("> ".to_owned() + tab);
|
||||||
|
Spans::from("> ".to_owned() + tab)
|
||||||
|
} else {
|
||||||
|
Spans::from(" ".to_owned() + tab)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
// There is only enough room to display 3 tabs of text
|
||||||
|
// So lets replace tabs not near the selected with "..."
|
||||||
|
let all_windows: Vec<&[Spans]> = titles
|
||||||
|
.windows(3)
|
||||||
|
.filter(|w| w.contains(&titles[tab_selected]))
|
||||||
|
.collect();
|
||||||
|
let first_window = all_windows[0];
|
||||||
|
let mut titles: Vec<Spans> = titles
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.map(
|
||||||
|
// Converts all tabs not in the window to "..."
|
||||||
|
|t| {
|
||||||
|
if first_window.contains(&t) {
|
||||||
|
t
|
||||||
|
} else {
|
||||||
|
Spans::from("...".to_owned())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
// Don't keep consecutive "..." tabs
|
||||||
|
titles.dedup();
|
||||||
|
// Now that the size of the titles vector has changed, need to re-locate the selected tab
|
||||||
|
let tab_selected_deduped: usize = titles
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.position(|span| span == span_selected)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
let tabs = Tabs::new(titles)
|
let tabs = Tabs::new(titles)
|
||||||
.block(Block::default()
|
.block(Block::default().title(Spans::from(Span::styled(
|
||||||
.title(
|
"Ultimate Training Modpack Menu",
|
||||||
Spans::from(
|
Style::default().fg(Color::LightRed),
|
||||||
Span::styled("Ultimate Training Modpack Menu",
|
))))
|
||||||
Style::default().fg(Color::LightRed)))))
|
|
||||||
.style(Style::default().fg(Color::White))
|
.style(Style::default().fg(Color::White))
|
||||||
.highlight_style(Style::default().fg(Color::Yellow))
|
.highlight_style(Style::default().fg(Color::Yellow))
|
||||||
.divider("|")
|
.divider("|")
|
||||||
.select(tab_selected);
|
.select(tab_selected_deduped);
|
||||||
|
|
||||||
let vertical_chunks = Layout::default()
|
let vertical_chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints(
|
||||||
Constraint::Length(2),
|
[
|
||||||
Constraint::Max(10),
|
Constraint::Length(2),
|
||||||
Constraint::Length(2)].as_ref())
|
Constraint::Max(10),
|
||||||
|
Constraint::Length(2),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
.split(f.size());
|
.split(f.size());
|
||||||
|
|
||||||
|
// Prevent overflow by adding a length constraint of NX_TUI_WIDTH
|
||||||
|
// Need to add a second constraint since the .expand_to_fill() method
|
||||||
|
// is not publicly exposed, and the attribute defaults to true.
|
||||||
|
// https://github.com/fdehau/tui-rs/blob/v0.19.0/src/layout.rs#L121
|
||||||
|
let vertical_chunks: Vec<Rect> = vertical_chunks
|
||||||
|
.iter()
|
||||||
|
.map(|chunk| {
|
||||||
|
Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(NX_TUI_WIDTH), // Width of the TUI terminal
|
||||||
|
Constraint::Min(0), // Fill the remainder margin
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(*chunk)[0]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
|
||||||
let list_chunks = Layout::default()
|
let list_chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Percentage(33), Constraint::Percentage(32), Constraint::Percentage(33)].as_ref())
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Percentage(33),
|
||||||
|
Constraint::Percentage(33),
|
||||||
|
Constraint::Percentage(33),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
.split(vertical_chunks[1]);
|
.split(vertical_chunks[1]);
|
||||||
|
|
||||||
f.render_widget(tabs, vertical_chunks[0]);
|
f.render_widget(tabs, vertical_chunks[0]);
|
||||||
|
@ -269,25 +614,33 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
|
||||||
if app.outer_list {
|
if app.outer_list {
|
||||||
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.menu_items.get(tab_selected).unwrap().lists.iter().enumerate() {
|
for (list_section, stateful_list) in app
|
||||||
|
.menu_items
|
||||||
|
.get(tab_selected)
|
||||||
|
.unwrap()
|
||||||
|
.lists
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
let items: Vec<ListItem> = stateful_list
|
let items: Vec<ListItem> = stateful_list
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let lines = vec![Spans::from(
|
let lines = vec![Spans::from(if stateful_list.state.selected().is_some() {
|
||||||
if stateful_list.state.selected().is_some() {
|
i.submenu_title.to_owned()
|
||||||
i.submenu_title.to_owned()
|
} else {
|
||||||
} else {
|
" ".to_owned() + i.submenu_title
|
||||||
" ".to_owned() + i.submenu_title
|
})];
|
||||||
})];
|
|
||||||
ListItem::new(lines).style(Style::default().fg(Color::White))
|
ListItem::new(lines).style(Style::default().fg(Color::White))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let list = List::new(items)
|
let list = List::new(items)
|
||||||
.block(Block::default()
|
.block(
|
||||||
.title(if list_section == 0 { "Options" } else { "" })
|
Block::default()
|
||||||
.style(Style::default().fg(Color::LightRed)))
|
.title(if list_section == 0 { "Options" } else { "" })
|
||||||
|
.style(Style::default().fg(Color::LightRed)),
|
||||||
|
)
|
||||||
.highlight_style(
|
.highlight_style(
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::Green)
|
.fg(Color::Green)
|
||||||
|
@ -305,63 +658,151 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
|
||||||
|
|
||||||
// TODO: Add Save Defaults
|
// TODO: Add Save Defaults
|
||||||
let help_paragraph = Paragraph::new(
|
let help_paragraph = Paragraph::new(
|
||||||
item_help.unwrap_or("").replace('\"', "") +
|
item_help.unwrap_or("").replace('\"', "")
|
||||||
"\nA: Enter sub-menu | B: Exit menu | ZL/ZR: Next tab"
|
+ "\nA: Enter sub-menu | B: Exit menu | ZL/ZR: Next tab",
|
||||||
).style(Style::default().fg(Color::Cyan));
|
)
|
||||||
|
.style(Style::default().fg(Color::Cyan));
|
||||||
f.render_widget(help_paragraph, vertical_chunks[2]);
|
f.render_widget(help_paragraph, vertical_chunks[2]);
|
||||||
} else {
|
} else {
|
||||||
let (title, help_text, mut sub_menu_str_lists) = app.sub_menu_strs_and_states();
|
if matches!(app.selected_sub_menu_slider.state, GaugeState::None) {
|
||||||
for list_section in 0..sub_menu_str_lists.len() {
|
let (title, help_text, mut sub_menu_str_lists) = app.sub_menu_strs_and_states();
|
||||||
let sub_menu_str = sub_menu_str_lists[list_section].0.clone();
|
for list_section in 0..sub_menu_str_lists.len() {
|
||||||
let sub_menu_state = &mut sub_menu_str_lists[list_section].1;
|
let sub_menu_str = sub_menu_str_lists[list_section].0.clone();
|
||||||
let values_items: Vec<ListItem> = sub_menu_str.iter().map(|s| {
|
let sub_menu_state = &mut sub_menu_str_lists[list_section].1;
|
||||||
ListItem::new(
|
let values_items: Vec<ListItem> = sub_menu_str
|
||||||
vec![
|
.iter()
|
||||||
Spans::from((if s.0 { "X " } else { " " }).to_owned() + s.1)
|
.map(|s| {
|
||||||
]
|
ListItem::new(vec![Spans::from(
|
||||||
)
|
(if s.0 { "X " } else { " " }).to_owned() + 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 } else { "" }))
|
||||||
.start_corner(Corner::TopLeft)
|
.start_corner(Corner::TopLeft)
|
||||||
.highlight_style(
|
.highlight_style(
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::LightGreen)
|
.fg(Color::LightGreen)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
)
|
)
|
||||||
.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(
|
||||||
|
help_text.replace('\"', "") + "\nA: Select toggle | B: Exit submenu",
|
||||||
|
)
|
||||||
|
.style(Style::default().fg(Color::Cyan));
|
||||||
|
f.render_widget(help_paragraph, vertical_chunks[2]);
|
||||||
|
} else {
|
||||||
|
let (_title, help_text, gauge_vals) = app.sub_menu_strs_for_slider();
|
||||||
|
let abs_min = gauge_vals.abs_min;
|
||||||
|
let abs_max = gauge_vals.abs_max;
|
||||||
|
let selected_min = gauge_vals.selected_min;
|
||||||
|
let selected_max = gauge_vals.selected_max;
|
||||||
|
let lbl_ratio = 0.95; // Needed so that the upper limit label is visible
|
||||||
|
let constraints = [
|
||||||
|
Constraint::Ratio((lbl_ratio * (selected_min-abs_min) as f32) as u32, 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
|
||||||
|
];
|
||||||
|
let gauge_chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(constraints)
|
||||||
|
.split(vertical_chunks[1]);
|
||||||
|
|
||||||
|
let slider_lbls = [
|
||||||
|
abs_min,
|
||||||
|
selected_min,
|
||||||
|
selected_max,
|
||||||
|
abs_max,
|
||||||
|
];
|
||||||
|
for (idx, lbl) in slider_lbls.iter().enumerate() {
|
||||||
|
let mut line_set = tui::symbols::line::NORMAL;
|
||||||
|
line_set.horizontal = "-";
|
||||||
|
let mut gauge = LineGauge::default()
|
||||||
|
.ratio(1.0)
|
||||||
|
.label(format!("{}", lbl))
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.line_set(line_set)
|
||||||
|
.gauge_style(Style::default().fg(Color::White).bg(Color::Black));
|
||||||
|
if idx == 1 {
|
||||||
|
// Slider between selected_min and selected_max
|
||||||
|
match gauge_vals.state {
|
||||||
|
GaugeState::MinHover => {
|
||||||
|
gauge = gauge.style(Style::default().fg(Color::Red))
|
||||||
|
}
|
||||||
|
GaugeState::MinSelected => {
|
||||||
|
gauge = gauge.style(Style::default().fg(Color::Green))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
gauge = gauge.gauge_style(Style::default().fg(Color::Yellow).bg(Color::Black));
|
||||||
|
} else if idx == 2 {
|
||||||
|
// Slider between selected_max and abs_max
|
||||||
|
match gauge_vals.state {
|
||||||
|
GaugeState::MaxHover => {
|
||||||
|
gauge = gauge.style(Style::default().fg(Color::Red))
|
||||||
|
}
|
||||||
|
GaugeState::MaxSelected => {
|
||||||
|
gauge = gauge.style(Style::default().fg(Color::Green))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else if idx == 3 {
|
||||||
|
// Slider for abs_max
|
||||||
|
// We only want the label to show, so set the line character to " "
|
||||||
|
let mut line_set = tui::symbols::line::NORMAL;
|
||||||
|
line_set.horizontal = " ";
|
||||||
|
gauge = gauge.line_set(line_set);
|
||||||
|
|
||||||
|
// For some reason, the selected_max slider displays on top
|
||||||
|
// So we need to change the abs_max slider styling to match
|
||||||
|
// If the selected_max is close enough to the abs_max
|
||||||
|
if (selected_max as f32 / abs_max as f32) > 0.95 {
|
||||||
|
gauge = gauge.style(match gauge_vals.state {
|
||||||
|
GaugeState::MaxHover => Style::default().fg(Color::Red),
|
||||||
|
GaugeState::MaxSelected => Style::default().fg(Color::Green),
|
||||||
|
_ => Style::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.render_widget(gauge, gauge_chunks[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let help_paragraph = Paragraph::new(
|
||||||
|
help_text.replace('\"', "") + "\nA: Select toggle | B: Exit submenu",
|
||||||
|
)
|
||||||
|
.style(Style::default().fg(Color::Cyan));
|
||||||
|
f.render_widget(help_paragraph, vertical_chunks[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let help_paragraph = Paragraph::new(
|
|
||||||
help_text.replace('\"', "") +
|
|
||||||
"\nA: Select toggle | B: Exit submenu"
|
|
||||||
).style(Style::default().fg(Color::Cyan));
|
|
||||||
f.render_widget(help_paragraph, vertical_chunks[2]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut settings = HashMap::new();
|
// Collect settings
|
||||||
|
let mut settings = Map::new();
|
||||||
// Collect settings for toggles
|
|
||||||
for key in app.menu_items.keys() {
|
for key in app.menu_items.keys() {
|
||||||
for list in &app.menu_items.get(key).unwrap().lists {
|
for list in &app.menu_items.get(key).unwrap().lists {
|
||||||
for sub_menu in &list.items {
|
for sub_menu in &list.items {
|
||||||
let val : usize = sub_menu.toggles.iter()
|
if !sub_menu.toggles.is_empty() {
|
||||||
.filter(|t| t.checked)
|
let val: u32 = sub_menu
|
||||||
.map(|t| t.toggle_value)
|
.toggles
|
||||||
.sum();
|
.iter()
|
||||||
|
.filter(|t| t.checked)
|
||||||
settings.insert(sub_menu.submenu_id, val);
|
.map(|t| t.toggle_value)
|
||||||
|
.sum();
|
||||||
|
settings.insert(sub_menu.submenu_id.to_string(), json!(val));
|
||||||
|
} else if sub_menu.slider.is_some() {
|
||||||
|
let s: &Slider = sub_menu.slider.as_ref().unwrap();
|
||||||
|
let val: Vec<u32> = vec![s.selected_min, s.selected_max];
|
||||||
|
settings.insert(sub_menu.submenu_id.to_string(), json!(val));
|
||||||
|
} else {
|
||||||
|
panic!("Could not collect settings for {:?}", sub_menu.submenu_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serde_json::to_string(&settings).unwrap()
|
serde_json::to_string(&settings).unwrap()
|
||||||
|
|
||||||
// TODO: Add saveDefaults
|
// TODO: Add saveDefaults
|
||||||
// if (document.getElementById("saveDefaults").checked) {
|
|
||||||
// url += "save_defaults=1";
|
|
||||||
// } else {
|
|
||||||
// url = url.slice(0, -1);
|
|
||||||
// }
|
|
||||||
}
|
}
|
|
@ -21,7 +21,6 @@ impl<T: Clone> MultiStatefulList<T> {
|
||||||
(self.total_len as f32 / self.lists.len() as f32).ceil() as usize * (list_section + 1),
|
(self.total_len as f32 / self.lists.len() as f32).ceil() as usize * (list_section + 1),
|
||||||
self.total_len);
|
self.total_len);
|
||||||
if (list_section_min_idx..list_section_max_idx).contains(&idx) {
|
if (list_section_min_idx..list_section_max_idx).contains(&idx) {
|
||||||
// println!("\n{}: ({}, {})", idx, list_section_min_idx, list_section_max_idx);
|
|
||||||
return (list_section, idx - list_section_min_idx)
|
return (list_section, idx - list_section_min_idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,8 @@ fn ensure_menu_retains_selections() -> Result<(), Box<dyn Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
let inputs = args.get(1);
|
||||||
let menu;
|
let menu;
|
||||||
unsafe {
|
unsafe {
|
||||||
menu = get_menu();
|
menu = get_menu();
|
||||||
|
@ -61,6 +63,21 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
#[cfg(not(feature = "has_terminal"))] {
|
#[cfg(not(feature = "has_terminal"))] {
|
||||||
let (mut terminal, mut app) = test_backend_setup(menu)?;
|
let (mut terminal, mut app) = test_backend_setup(menu)?;
|
||||||
|
if inputs.is_some() {
|
||||||
|
inputs.unwrap().split(",").for_each(|input| {
|
||||||
|
match input.to_uppercase().as_str() {
|
||||||
|
"L" => app.on_l(),
|
||||||
|
"R" => app.on_r(),
|
||||||
|
"A" => app.on_a(),
|
||||||
|
"B" => app.on_b(),
|
||||||
|
"UP" => app.on_up(),
|
||||||
|
"DOWN" => app.on_down(),
|
||||||
|
"LEFT" => app.on_left(),
|
||||||
|
"RIGHT" => app.on_right(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
let mut json_response = String::new();
|
let mut json_response = String::new();
|
||||||
let frame_res = terminal.draw(|f| json_response = training_mod_tui::ui(f, &mut app))?;
|
let frame_res = terminal.draw(|f| json_response = training_mod_tui::ui(f, &mut app))?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue