mirror of
https://github.com/jugeeya/UltimateTrainingModpack.git
synced 2024-11-24 02:44:17 +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(
|
||||
0x70,
|
||||
"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 {
|
||||
|
|
21
src/lib.rs
21
src/lib.rs
|
@ -54,6 +54,27 @@ macro_rules! c_str {
|
|||
|
||||
#[skyline::main(name = "training_modpack")]
|
||||
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 {
|
||||
($($arg:tt)*) => {
|
||||
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;
|
||||
}
|
||||
|
||||
:focus {
|
||||
:focus:not(.noUi-handle),
|
||||
.handleSelected,
|
||||
.noUi-connect
|
||||
{
|
||||
background: rgb(255, 70, 2);
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
|
@ -302,3 +305,38 @@ body {
|
|||
text-align: center;
|
||||
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
|
||||
if (isNx) {
|
||||
window.nx.footer.setAssign('A', '', function () { select(document.activeElement) }, { se: '' });
|
||||
window.nx.footer.setAssign('B', '', closeOrExit, { se: '' });
|
||||
window.nx.footer.setAssign('X', '', resetCurrentMenu, { se: '' });
|
||||
window.nx.footer.setAssign('L', '', resetAllMenus, { se: '' });
|
||||
window.nx.footer.setAssign('R', '', saveDefaults, { se: '' });
|
||||
window.nx.footer.setAssign('ZR', '', cycleNextTab, { se: '' });
|
||||
window.nx.footer.setAssign('ZL', '', cyclePrevTab, { se: '' });
|
||||
window.nx.addEventListener("message", function(msg) { setSettingsFromJSON(msg)});
|
||||
window.nx.sendMessage("loaded");
|
||||
window.nx.addEventListener("message", function (msg) { setSettingsFromJSON(msg.data) });
|
||||
document.addEventListener('keydown', (event) => {
|
||||
switch (event.keyCode) {
|
||||
case 37: // Control stick left
|
||||
decreaseSelectedHandle();
|
||||
break;
|
||||
case 39: // Control stick right
|
||||
increaseSelectedHandle();
|
||||
break;
|
||||
}
|
||||
})
|
||||
} else {
|
||||
document.addEventListener('keypress', (event) => {
|
||||
document.addEventListener('keydown', (event) => {
|
||||
switch (event.key) {
|
||||
case 'a':
|
||||
console.log('a');
|
||||
select(document.activeElement);
|
||||
break;
|
||||
case 'b':
|
||||
console.log('b');
|
||||
closeOrExit();
|
||||
|
@ -58,6 +72,14 @@ if (isNx) {
|
|||
console.log('o');
|
||||
cyclePrevTab();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
console.log('ArrowLeft');
|
||||
decreaseSelectedHandle();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
console.log('ArrowRight');
|
||||
increaseSelectedHandle();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -65,6 +87,13 @@ if (isNx) {
|
|||
const onLoad = () => {
|
||||
// Activate the first tab
|
||||
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;
|
||||
|
@ -73,6 +102,8 @@ var settings;
|
|||
var defaultSettings;
|
||||
|
||||
var lastFocusedItem = document.querySelector('.menu-item > button');
|
||||
var selectedSliderHandle = -1;
|
||||
|
||||
const currentTabContent = () => {
|
||||
const currentActiveTab = document.querySelector('.tab-button.active');
|
||||
|
||||
|
@ -114,7 +145,11 @@ const openMenuItem = (eventTarget) => {
|
|||
currentTabContent().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;
|
||||
};
|
||||
|
@ -195,6 +230,19 @@ const exit = () => {
|
|||
};
|
||||
|
||||
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
|
||||
if (document.querySelector('.modal:not(.hide)')) {
|
||||
console.log('Closing Items');
|
||||
|
@ -210,52 +258,12 @@ function closeOrExit() {
|
|||
|
||||
function setSettingsFromJSON(msg) {
|
||||
// Receive a menu message and set settings
|
||||
var msg_json = JSON.parse(msg.data);
|
||||
var msg_json = JSON.parse(msg);
|
||||
settings = msg_json["menu"];
|
||||
defaultSettings = msg_json["defaults_menu"];
|
||||
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) {
|
||||
// Deselect all options in the submenu
|
||||
const parent = eventTarget.parentElement;
|
||||
|
@ -277,19 +285,26 @@ const isValueInBitmask = (value, mask) => (mask & value) != 0;
|
|||
const setOptionsForMenu = (menuId) => {
|
||||
const modal = document.querySelector(`.modal[data-id="${menuId}"]`);
|
||||
|
||||
modal.querySelectorAll('.menu-icon').forEach(function (toggle) {
|
||||
if (isValueInBitmask(toggle.dataset.val, settings[menuId])) {
|
||||
toggle.classList.remove('hidden');
|
||||
} else {
|
||||
toggle.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
if (modal.querySelector('.modal-button')) {
|
||||
// Toggle menu
|
||||
modal.querySelectorAll('.menu-icon').forEach(function (toggle) {
|
||||
if (isValueInBitmask(toggle.dataset.val, settings[menuId])) {
|
||||
toggle.classList.remove('hidden');
|
||||
} else {
|
||||
toggle.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
if (modal.classList.contains('single-option')) {
|
||||
// If no option is selected default to the first option
|
||||
if (modal.querySelectorAll('.menu-icon:not(.hidden)').length === 0) {
|
||||
selectSingleOption(modal.querySelector('button'));
|
||||
if (modal.classList.contains('single-option')) {
|
||||
// If no option is selected default to the first option
|
||||
if (modal.querySelectorAll('.menu-icon:not(.hidden)').length === 0) {
|
||||
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));
|
||||
}
|
||||
|
||||
function getMaskFromMenuID(id) {
|
||||
var value = 0;
|
||||
function getSettingsValFromMenuID(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) {
|
||||
value += parseInt(toggle.dataset.val);
|
||||
});
|
||||
|
||||
return value;
|
||||
options.forEach(function (toggle) {
|
||||
value += parseInt(toggle.dataset.val);
|
||||
});
|
||||
return value;
|
||||
} else {
|
||||
// Slider menu
|
||||
// Return value is a [lower,upper] array
|
||||
slider = modal.querySelector('.modal-slider');
|
||||
return getSliderVals(slider);
|
||||
}
|
||||
}
|
||||
|
||||
function resetCurrentMenu() {
|
||||
|
@ -315,10 +338,11 @@ function resetCurrentMenu() {
|
|||
const menu = document.querySelector('.modal:not(.hide)');
|
||||
|
||||
const menuId = menu.dataset.id;
|
||||
const defaultSectionMask = defaultSettings[menuId];
|
||||
const defaultSubmenuSetting = defaultSettings[menuId];
|
||||
|
||||
settings[menuId] = defaultSectionMask;
|
||||
settings[menuId] = defaultSubmenuSetting;
|
||||
|
||||
deselectSliderHandles();
|
||||
populateMenuFromSettings();
|
||||
}
|
||||
|
||||
|
@ -327,10 +351,11 @@ function resetAllMenus() {
|
|||
if (confirm('Are you sure that you want to reset all menu settings to the default?')) {
|
||||
document.querySelectorAll('.menu-item').forEach(function (item) {
|
||||
const defaultMenuId = item.id;
|
||||
const defaultMask = defaultSettings[defaultMenuId];
|
||||
const defaultSubmenuSetting = defaultSettings[defaultMenuId];
|
||||
|
||||
settings[item.id] = defaultMask;
|
||||
settings[item.id] = defaultSubmenuSetting;
|
||||
|
||||
deselectSliderHandles();
|
||||
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?')) {
|
||||
document.querySelectorAll('.menu-item').forEach((item) => {
|
||||
const menu = item.id;
|
||||
|
||||
defaultSettings[menu] = getMaskFromMenuID(item.id);
|
||||
defaultSettings[menu] = getSettingsValFromMenuID(item.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function cycleNextTab() {
|
||||
deselectSliderHandles();
|
||||
// Cycle to the next tab
|
||||
const activeTab = document.querySelector('.tab-button.active');
|
||||
var nextTab = activeTab.nextElementSibling;
|
||||
|
@ -362,6 +387,7 @@ function cycleNextTab() {
|
|||
}
|
||||
|
||||
function cyclePrevTab() {
|
||||
deselectSliderHandles();
|
||||
// Cycle to the previous tab
|
||||
const activeTab = document.querySelector('.tab-button.active');
|
||||
var previousTab = activeTab.previousElementSibling;
|
||||
|
@ -372,3 +398,124 @@ function cyclePrevTab() {
|
|||
}
|
||||
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 name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title>Modpack Menu</title>
|
||||
<link rel="stylesheet" href="./css/nouislider.min.css">
|
||||
<link rel="stylesheet" href="./css/training_modpack.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script defer src="./js/nouislider.min.js"></script>
|
||||
<script defer src="./js/training_modpack.js"></script>
|
||||
<div class="header">
|
||||
<a id="ret-button" tabindex="-1" class="return-icon-container" onclick="closeOrExit()">
|
||||
|
@ -42,6 +44,20 @@
|
|||
</div>
|
||||
</button>
|
||||
{{/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>
|
||||
{{/tab_submenus}}
|
||||
<div id="{{tab_id}}_tab" class="tab-content hide">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::common::button_config;
|
||||
use crate::common::consts::get_random_float;
|
||||
use crate::common::consts::get_random_int;
|
||||
use crate::common::consts::FighterId;
|
||||
use crate::common::consts::OnOff;
|
||||
|
@ -15,7 +16,7 @@ use smash::app::{self, lua_bind::*, Item};
|
|||
use smash::hash40;
|
||||
use smash::lib::lua_const::*;
|
||||
use smash::phx::{Hash40, Vector3f};
|
||||
use training_mod_consts::CharacterItem;
|
||||
use training_mod_consts::{CharacterItem, SaveDamage};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum SaveState {
|
||||
|
@ -149,16 +150,6 @@ pub unsafe fn get_param_int(
|
|||
}
|
||||
|
||||
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 {
|
||||
DamageModule::heal(
|
||||
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 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
|
||||
if !is_cpu && !fighter_is_nana && MENU.character_item != CharacterItem::None {
|
||||
apply_item(MENU.character_item);
|
||||
|
|
File diff suppressed because it is too large
Load diff
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,
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,8 @@ fn ensure_menu_retains_selections() -> 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;
|
||||
unsafe {
|
||||
menu = get_menu();
|
||||
|
@ -61,6 +63,21 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
#[cfg(not(feature = "has_terminal"))] {
|
||||
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 frame_res = terminal.draw(|f| json_response = training_mod_tui::ui(f, &mut app))?;
|
||||
|
||||
|
|
Loading…
Reference in a new issue