diff --git a/assets/images/menus/options/meter.png b/assets/images/menus/options/meter.png
deleted file mode 100644
index 4607e9b0..00000000
Binary files a/assets/images/menus/options/meter.png and /dev/null differ
diff --git a/assets/images/menus/options/meter.xml b/assets/images/menus/options/meter.xml
deleted file mode 100644
index fddf0e4e..00000000
--- a/assets/images/menus/options/meter.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/assets/images/menus/options/slider.png b/assets/images/menus/options/slider.png
new file mode 100644
index 00000000..e2bf2192
Binary files /dev/null and b/assets/images/menus/options/slider.png differ
diff --git a/assets/images/menus/options/slider.xml b/assets/images/menus/options/slider.xml
new file mode 100644
index 00000000..facddef9
--- /dev/null
+++ b/assets/images/menus/options/slider.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/source/funkin/backend/TurboControls.hx b/source/funkin/backend/TurboControls.hx
new file mode 100644
index 00000000..2c1f4ec0
--- /dev/null
+++ b/source/funkin/backend/TurboControls.hx
@@ -0,0 +1,121 @@
+package funkin.backend;
+
+import flixel.input.actions.FlxAction;
+import flixel.input.gamepad.FlxGamepadInputID;
+import flixel.input.gamepad.FlxGamepad;
+import flixel.input.keyboard.FlxKey;
+import flixel.input.FlxInput.FlxInputState;
+import flixel.FlxBasic;
+
+import funkin.backend.system.Controls;
+import funkin.options.PlayerSettings;
+
+class TurboBasic extends FlxBasic {
+ public static inline final DEFAULT_DELAY:Float = 0.4;
+ public static inline final DEFAULT_INTERVAL:Float = 1 / 18;
+
+ public var delay:Float;
+ public var interval:Float;
+ public var activated(default, null):Bool;
+ public var pressed(get, never):Bool; function get_pressed() return false;
+ public var allPress:Bool;
+
+ var time:Float = 0;
+
+ public function new(delay = DEFAULT_DELAY, interval = DEFAULT_INTERVAL, allPress = false) {
+ super();
+ this.delay = delay;
+ this.interval = interval;
+ this.allPress = allPress;
+ }
+
+ override function update(elapsed:Float) {
+ super.update(elapsed);
+
+ if (pressed) {
+ if (time == 0) activated = true;
+ else if (activated = (time >= delay + interval)) time -= interval;
+ time += elapsed;
+ }
+ else {
+ activated = false;
+ time = 0;
+ }
+ }
+}
+
+class TurboControls extends TurboBasic {
+ public var controlsInstance:Controls;
+ public var controls:Array;
+ public function new(controls:Array, ?controlsInstance:Controls, ?delay:Float, ?interval:Float, ?allPress:Bool) {
+ super(delay, interval, allPress);
+ this.controls = controls;
+ this.controlsInstance = controlsInstance != null ? controlsInstance : PlayerSettings.solo.controls;
+ }
+
+ override function get_pressed() {
+ if (allPress) {
+ for (control in controls) if (!controlsInstance.getActionFromControl(control).check()) return false;
+ }
+ else {
+ for (control in controls) if (controlsInstance.getActionFromControl(control).check()) return true;
+ }
+ return allPress;
+ }
+}
+
+class TurboActions extends TurboBasic {
+ public var actions:Array;
+ public function new(actions:Array, ?delay:Float, ?interval:Float, ?allPress:Bool) {
+ super(delay, interval, allPress);
+ this.actions = actions;
+ }
+
+ override function get_pressed() {
+ if (allPress) {
+ for (action in actions) if (!action.check()) return false;
+ }
+ else {
+ for (action in actions) if (action.check()) return true;
+ }
+ return allPress;
+ }
+}
+
+class TurboKeys extends TurboBasic {
+ public var keys:Array;
+ public function new(keys:Array, ?delay:Float, ?interval:Float, ?allPress:Bool) {
+ super(delay, interval, allPress);
+ this.keys = keys;
+ }
+
+ override function get_pressed() {
+ if (allPress) {
+ for (key in keys) if (!FlxG.keys.checkStatus(key, PRESSED)) return false;
+ }
+ else {
+ for (key in keys) if (FlxG.keys.checkStatus(key, PRESSED)) return true;
+ }
+ return allPress;
+ }
+}
+
+class TurboButtons extends TurboBasic {
+ public var inputs:Array;
+ public var gamepad:FlxGamepad;
+ public function new(inputs:Array, ?gamepad:FlxGamepad, ?delay:Float, ?interval:Float, ?allPress:Bool) {
+ super(delay, interval, allPress);
+ this.inputs = inputs;
+ this.gamepad = gamepad ?? FlxG.gamepads.firstActive;
+ }
+
+ override function get_pressed() {
+ if (allPress) {
+ for (input in inputs) if (!gamepad.checkStatus(input, PRESSED)) return false;
+ }
+ else {
+ for (input in inputs) if (gamepad.checkStatus(input, PRESSED)) return true;
+ }
+ return allPress;
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/backend/system/Controls.hx b/source/funkin/backend/system/Controls.hx
index 0778469d..0ccaac63 100644
--- a/source/funkin/backend/system/Controls.hx
+++ b/source/funkin/backend/system/Controls.hx
@@ -191,6 +191,8 @@ class Controls extends FlxActionSet
setKeyboardScheme(scheme, false);
}
+ public function getActionFromControl(control:Control):FLxAction return macro_getActionFromControl(control);
+
public function getKeyName(control:Control, idx:Int = 0):String
{
var action = macro_getActionFromControl(control);
diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx
index d24e153a..47510764 100644
--- a/source/funkin/backend/utils/CoolUtil.hx
+++ b/source/funkin/backend/utils/CoolUtil.hx
@@ -519,8 +519,8 @@ final class CoolUtil
* @param menuSFX Menu SFX to play
* @param volume At which volume it should play
*/
- @:noUsing public static inline function playMenuSFX(menuSFX:CoolSfx = SCROLL, volume:Float = 1) {
- FlxG.sound.play(Paths.sound('menu/' + switch(menuSFX) {
+ @:noUsing public static inline function playMenuSFX(menuSFX:CoolSfx = SCROLL, volume:Float = 1):FlxSound {
+ return FlxG.sound.play(Paths.sound('menu/' + switch(menuSFX) {
case CONFIRM: 'confirm';
case CANCEL: 'cancel';
case SCROLL: 'scroll';
diff --git a/source/funkin/menus/ui/Alphabet.hx b/source/funkin/menus/ui/Alphabet.hx
index c3508c31..fcc447a9 100644
--- a/source/funkin/menus/ui/Alphabet.hx
+++ b/source/funkin/menus/ui/Alphabet.hx
@@ -167,6 +167,7 @@ class Alphabet extends FlxSprite {
// for menu shit
public var targetY:Float = 0;
public var isMenuItem:Bool = false;
+ public var itemHeight:Float = 120;
public function new(?x:Float, ?y:Float, ?text:String = "", ?font:OneOfTwo = "normal") {
super(x, y);
@@ -202,7 +203,7 @@ class Alphabet extends FlxSprite {
if (isMenuItem) {
var scaledY = targetY * 1.3;
- y = CoolUtil.fpsLerp(y, (scaledY * 120) + (FlxG.height - height) * 0.5, 0.16);
+ y = CoolUtil.fpsLerp(y, (scaledY * itemHeight) + (FlxG.height - height) * 0.5, 0.16);
x = CoolUtil.fpsLerp(x, (targetY * 20) + 90, 0.16);
}
}
diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx
index a60d519d..bd39cd45 100644
--- a/source/funkin/options/Options.hx
+++ b/source/funkin/options/Options.hx
@@ -209,7 +209,8 @@ class Options
applyKeybinds();
FlxG.game.stage.quality = (FlxG.enableAntialiasing = antialiasing) ? LOW : BEST;
FlxG.autoPause = autoPause;
- FlxG.drawFramerate = FlxG.updateFramerate = framerate;
+ if (FlxG.updateFramerate < framerate) FlxG.drawFramerate = FlxG.updateFramerate = framerate;
+ else FlxG.updateFramerate = FlxG.drawFramerate = framerate;
}
public static function applyKeybinds() {
diff --git a/source/funkin/options/OptionsMenu.hx b/source/funkin/options/OptionsMenu.hx
index ace34aad..9f35c36f 100644
--- a/source/funkin/options/OptionsMenu.hx
+++ b/source/funkin/options/OptionsMenu.hx
@@ -1,172 +1,87 @@
package funkin.options;
-import funkin.backend.system.framerate.Framerate;
-import funkin.options.TreeMenu;
import funkin.options.categories.*;
import funkin.options.type.*;
-import funkin.options.type.Checkbox;
-import haxe.xml.Access;
+
+typedef OptionCategory = {
+ var name:String;
+ var desc:String;
+ var ?state:OneOfThree, (name:String, desc:String) -> OptionsScreen>;
+ var ?substate:OneOfThree, (name:String, desc:String) -> MusicBeatSubstate>;
+ var ?suffix:String;
+}
class OptionsMenu extends TreeMenu {
public static var mainOptions:Array = [
{ // name and desc are actually the translations ids! - Nex
name: 'optionsTree.controls-name',
desc: 'optionsTree.controls-desc',
- state: null,
- substate: (name:String, desc:String) -> {
- return new funkin.options.keybinds.KeybindsOptions();
- }
+ suffix: '';
+ substate: funkin.options.keybinds.KeybindsOptions;
},
{
name: 'optionsTree.gameplay-name',
desc: 'optionsTree.gameplay-desc',
- suffix: " >",
state: GameplayOptions
},
{
name: 'optionsTree.appearance-name',
desc: 'optionsTree.appearance-desc',
- suffix: " >",
state: AppearanceOptions
},
#if TRANSLATIONS_SUPPORT
{
name: 'optionsTree.language-name',
desc: 'optionsTree.language-desc',
- suffix: " >",
state: LanguageOptions
},
#end
{
name: 'optionsTree.miscellaneous-name',
desc: 'optionsTree.miscellaneous-desc',
- suffix: " >",
state: MiscOptions
}
];
- var bg:FlxSprite;
+ var bg:FunkinSprite;
- public override function create() {
+ override function create() {
super.create();
CoolUtil.playMenuSong();
DiscordUtil.call("onMenuLoaded", ["Options Menu"]);
- bg = new FlxSprite(-80).loadAnimatedGraphic(Paths.image('menus/menuBGBlue'));
- // bg.scrollFactor.set();
- bg.scale.set(1.15, 1.15);
- bg.updateHitbox();
- bg.screenCenter();
- bg.scrollFactor.set();
+ add(bg = new FunkinSprite().loadAnimatedGraphic(Paths.image('menus/menuBGBlue')));
bg.antialiasing = true;
- add(bg);
+ bg.scrollFactor.set();
+ updateBG();
- main = new OptionsScreen("optionsMenu.header.title", "optionsMenu.header.desc", [for(o in mainOptions) {
- new TextOption(o.name, o.desc, o.suffix, function() {
- if (o.substate != null) {
- persistentUpdate = false;
- persistentDraw = true;
- if (o.substate is MusicBeatSubstate) {
- openSubState(o.substate);
- } else if(Reflect.isFunction(o.substate)) {
- var substate:(name:String, desc:String) -> MusicBeatSubstate = o.substate;
- openSubState(substate(o.name, o.desc));
- } else { // o.substate is Class
- openSubState(Type.createInstance(o.substate, [o.name, o.desc]));
- }
- } else {
- if (o.state is OptionsScreen) {
- optionsTree.add(o.state);
- } else if(Reflect.isFunction(o.state)) {
- var state:(name:String, desc:String) -> OptionsScreen = o.state;
- optionsTree.add(state(o.name, o.desc));
- } else { // o.state is Class
- optionsTree.add(Type.createInstance(o.state, [o.name, o.desc]));
- }
- }
- });
- }]);
+ /*tree = [
+ for (o in mainOptions) {
- for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) {
- var xmlPath = Paths.xml('config/options/LIB_$i');
-
- if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT")) {
- var access:Access = null;
- try access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT")))
- catch(e) Logs.trace('Error while parsing options.xml: ${Std.string(e)}', ERROR);
- if (access != null) for (o in parseOptionsFromXML(access)) main.add(o);
}
- }
+ ];*/
+
}
- public function reloadStrings() {
- for(o in main.members) {
- o.reloadStrings();
- }
- optionsTree.reloadStrings();
- reloadLabels();
+ public function updateBG() {
+ var scaleX:Float = FlxG.width / bg.width;
+ var scaleY:Float = FlxG.height / bg.height;
+ bg.scale.x = bg.scale.y = Math.max(scaleX, scaleY);
+ bg.screenCenter();
}
- public override function exit() {
+ override function onResize(width:Int, height:Int) {
+ super.onResize(width, height);
+ if (!UIState.resolutionAware) return;
+
+ updateBG();
+ }
+
+ override function exit() {
Options.save();
Options.applySettings();
super.exit();
}
-
- /**
- * XML STUFF
- */
- public function parseOptionsFromXML(xml:Access):Array {
- var options:Array = [];
-
- for(node in xml.elements) {
- if (!node.has.name) {
- Logs.warn("An option node requires a name attribute.");
- continue;
- }
- var name = node.getAtt("name");
- var desc = node.getAtt("desc").getDefault("optionsMenu.desc-missing");
-
- switch(node.name) {
- case "checkbox":
- if (!node.has.id) {
- Logs.warn("A checkbox option requires an \"id\" for option saving.");
- continue;
- }
- options.push(new Checkbox(name, desc, node.att.id, FlxG.save.data));
-
- case "number":
- if (!node.has.id) {
- Logs.warn("A number option requires an \"id\" for option saving.");
- continue;
- }
- options.push(new NumOption(name, desc, Std.parseFloat(node.att.min), Std.parseFloat(node.att.max), Std.parseFloat(node.att.change), node.att.id, null, FlxG.save.data));
- case "choice":
- if (!node.has.id) {
- Logs.warn("A choice option requires an \"id\" for option saving.");
- continue;
- }
-
- var optionOptions:Array = [];
- var optionDisplayOptions:Array = [];
-
- for(choice in node.elements) {
- optionOptions.push(choice.att.value);
- optionDisplayOptions.push(choice.att.name);
- }
-
- if(optionOptions.length > 0)
- options.push(new ArrayOption(name, desc, optionOptions, optionDisplayOptions, node.att.id, null, FlxG.save.data));
-
- case "menu":
- options.push(new TextOption(name + " >", desc, function() {
- optionsTree.add(new OptionsScreen(name, desc, parseOptionsFromXML(node)));
- }));
- }
- }
-
- return options;
- }
}
\ No newline at end of file
diff --git a/source/funkin/options/OptionsTree.hx b/source/funkin/options/OptionsTree.hx
deleted file mode 100644
index 9fd9e590..00000000
--- a/source/funkin/options/OptionsTree.hx
+++ /dev/null
@@ -1,89 +0,0 @@
-package funkin.options;
-
-class OptionsTree extends FlxTypedGroup {
- public var lastMenu:OptionsScreen;
- public var treeParent:TreeMenu;
- public var wasClosing:Bool = false;
- //public override function new() {
- // super();
- //}
-
- public override function update(elapsed:Float) {
- var last = members.last();
- if (last != null)
- last.update(elapsed);
- }
-
- public function updateAll(elapsed:Float) {
- for (member in members) {
- if(member == null) continue;
- if(member.active && member.exists) {
- member.update(elapsed);
- }
- }
- }
-
- public override function draw() {
- super.draw();
- if (lastMenu != null) { // manually draw lastMenu since it got removed from the members list
- lastMenu.draw();
- }
- }
-
- public function reloadStrings() {
- for(o in members) {
- o.reloadStrings();
- }
- }
-
- public override function add(m:OptionsScreen) {
- super.add(m);
- setup(m);
- clearLastMenu();
- wasClosing = false;
- onMenuChange();
- return m;
- }
- public override function insert(pos:Int, m:OptionsScreen) {
- var last = members.last();
- super.insert(pos, m);
- setup(m);
- if (last != members.last()) {
- wasClosing = false;
- onMenuChange();
- }
- return m;
- }
-
- public function setup(m:OptionsScreen) {
- m.onClose = __subMenuClose;
- m.id = members.indexOf(m);
- m.parent = this;
- m.update(0);
- }
-
- function __subMenuClose(m:OptionsScreen) {
- wasClosing = true;
- clearLastMenu();
- lastMenu = m;
- remove(m, true);
- onMenuChange();
- onMenuClose(m);
- }
-
- public function clearLastMenu() {
- lastMenu = FlxDestroyUtil.destroy(lastMenu);
- }
-
- public override function destroy() {
- super.destroy();
- lastMenu = FlxDestroyUtil.destroy(lastMenu);
- }
-
- public dynamic function onMenuChange() {
-
- }
-
- public dynamic function onMenuClose(m:OptionsScreen) {
- }
-}
\ No newline at end of file
diff --git a/source/funkin/options/TreeMenu.hx b/source/funkin/options/TreeMenu.hx
index 41717e88..ba818c36 100644
--- a/source/funkin/options/TreeMenu.hx
+++ b/source/funkin/options/TreeMenu.hx
@@ -1,148 +1,269 @@
package funkin.options;
-import flixel.FlxState;
-import flixel.tweens.FlxTween;
-import flixel.util.typeLimit.OneOfTwo;
-import flixel.util.typeLimit.OneOfThree;
-import funkin.backend.FunkinText;
-import funkin.editors.ui.UIState;
-import funkin.menus.MainMenuState;
-import funkin.options.type.OptionType;
-import funkin.options.type.TextOption;
+import flixel.util.FlxSignal;
import funkin.backend.system.framerate.Framerate;
+import funkin.editors.ui.UIState;
+
+interface ITreeOption {
+ var desc:String;
+ var selected:Bool;
+
+ function changeSelection(change:Int);
+ function select():Void;
+}
class TreeMenu extends UIState {
- public var main:OptionsScreen;
- public var optionsTree:OptionsTree;
- public var pathLabel:FunkinText;
- public var pathDesc:FunkinText;
- public var pathBG:FlxSprite;
+ public var onMenuClosed:FlxTypedSignalVoid> = new FlxTypedSignal();
+ public var onMenuChanged:FlxTypedSignalVoid> = new FlxTypedSignal();
- public var screenScroll(default, set):Float;
- private inline function set_screenScroll(val:Float) {
- FlxG.camera.scroll.x = val * FlxG.camera.width;
- return screenScroll = val;
- }
+ public var tree(default, null):Array;
+ public var length(default, null):Int = 0;
+ public var lastMenu:Null;
+ public var destroyMenus:Bool = true;
- public static var lastState:Class = null; // Static for fixing the softlock bugs when resetting the state - Nex
+ public var exitCallback:TreeMenu->Void;
- public function new(scriptsAllowed:Bool = true, ?scriptName:String) {
- if (lastState == null) lastState = Type.getClass(FlxG.state);
+ public var titleLabel:FunkinText;
+ public var descLabel:FunkinText;
+ public var bgLabel:FlxSprite;
+
+ var menuChangeTween:FlxTween;
+ var _drawer:TreeMenuDrawer;
+
+ public function new(exitCallback:TreeMenu->Void,
+ scriptsAllowed:Bool = true, ?scriptName:String)
+ {
super(scriptsAllowed, scriptName);
+ this.exitCallback = exitCallback;
}
- public override function createPost() {
- if (main == null) main = new OptionsScreen("Fallback Treemenu", "Please set the \"main\" variable in your extended class before createPost", [new TextOption("Oops! No Options", "This doesn't look like it was supposed to happen...", () -> FlxG.resetState() ) ]);
+ override function create() {
+ super.create();
- screenScroll = -1;
+ bgLabel = new FlxSprite().makeGraphic(1, 1, FlxColor.BLACK);
+ bgLabel.alpha = 0.25;
+ bgLabel.scrollFactor.set();
- pathLabel = new FunkinText(4, 4, FlxG.width - 8, "> Tree Menu", 32, true);
- pathLabel.borderSize = 1.25;
- pathLabel.scrollFactor.set();
-
- pathDesc = new FunkinText(4, pathLabel.y + pathLabel.height + 2, FlxG.width - 8, "Current Tree Menu Description", 16, true);
- pathDesc.scrollFactor.set();
-
- pathBG = new FlxSprite().makeGraphic(1, 1, 0xFF000000);
- pathBG.scale.set(FlxG.width, pathDesc.y + pathDesc.height + 2);
- pathBG.updateHitbox();
- pathBG.alpha = 0.25;
- pathBG.scrollFactor.set();
-
- optionsTree = new OptionsTree();
- optionsTree.onMenuChange = onMenuChange;
- optionsTree.onMenuClose = onMenuClose;
- optionsTree.treeParent = this;
- optionsTree.add(main);
-
-
- add(optionsTree);
- add(pathBG);
- add(pathLabel);
- add(pathDesc);
+ titleLabel = new FunkinText(4, 4, FlxG.camera.width - 8, 32);
+ titleLabel.borderSize = 1.25;
+ titleLabel.scrollFactor.set();
+ descLabel = new FunkinText(4, 0, FlxG.camera.width - 8, 16);
+ descLabel.scrollFactor.set();
+ }
+ override function createPost() {
super.createPost();
+
+ if ((length = tree.length) != 0) {
+ updateMenuPositions(true);
+ tree.last().inputEnabled = true;
+ }
+ else
+ add(new TreeMenuScreen("Fallback TreeMenuScreen", "Please insert menus into \"tree\" variable in your extended class in create or before createPost"));
+
+ add(_drawer = new TreeMenuDrawer(this));
+ add(bgLabel);
+ add(titleLabel);
+ add(descLabel);
+ updateLabels();
}
- public function onMenuChange() {
- if (optionsTree.members.length <= 0) {
- exit();
- } else {
- if (menuChangeTween != null)
- menuChangeTween.cancel();
+ public function updateMenuPositions(fromIndex:Int = 0, cameraScroll = false) {
+ var last = tree[position - 1], menu:TreeMenuScreen;
+ while (fromIndex < length) if ((menu = tree[fromIndex++]) != null) {
+ menu.x = last == null ? 0 : last.x + Math.max(FlxG.camera.width, last.width);
+ last = menu;
+ }
- menuChangeTween = FlxTween.num(screenScroll, Math.max(0, (optionsTree.members.length-1)), 1.5, {ease: menuTransitionEase, onComplete: function(t) {
- optionsTree.clearLastMenu();
- menuChangeTween = null;
- }}, (val:Float) -> screenScroll = val);
+ if (menuChangeTween != null && menuChangeTween.active) menuChangeTween.cancel();
+ if (cameraScroll) FlxG.camera.scroll.x = lastMenu.x;
+ }
- reloadLabels();
+ public function updateLabels() {
+ var s = "", last = tree.last();
+ for (menu in tree) if (menu != null) s += menu.name + " > ";
+
+ titleLabel.text = s;
+ descLabel.y = titleLabel.y + titleLabel.height + 2;
+
+ updateDesc();
+ }
+
+ public function updateDesc(?customText:String) {
+ var last = tree.last();
+
+ descLabel.text = last.desc;
+ if (customText != null && customText.length > 0) descLabel.text += "\n" + customText;
+ else if (last.curSelected >= 0 && last.curSelected < last.length && last.members[last.curSelected] is ITreeOption)
+ descLabel.text += "\n" + (cast last.members[last.curSelected]:ITreeOption).desc;
+
+ bgLabel.scale.set(FLxG.width, descLabel.y + descLabel.height + 2);
+ bgLabel.updateHitbox();
+
+ Framerate.offset.y = bgLabel.height + 2;
+ }
+
+ public function add(menu:TreeMenuScreen):TreeMenuScreen {
+ if (menu == null) return null;
+ if (tree.indexOf(menu) != -1) return menu;
+
+ tree.push(menu);
+ menu.parent = this;
+ length++;
+
+ var prev = tree[length - 1];
+ menu.x = prev == null ? 0 : prev.x + Math.max(FlxG.camera.width, prev.width);
+
+ menuChanged();
+ menu.inputEnabled = true;
+ prev.inputEnabled = false;
+
+ return menu;
+ }
+
+ public function insert(position:Int, menu:TreeMenuScreen):TreeMenuScreen {
+ if (menu == null) return null;
+ if (tree.indexOf(menu) != -1) return menu;
+
+ if (position < 0) position = length - ((-position - 1) % length);
+ tree.insert(position, menu);
+ menu.parent = this;
+
+ var lastChanged = position >= length++;
+ updateMenuPositions(position, !lastChanged);
+
+ if (lastChanged) {
+ menuChanged();
+ menu.inputEnabled = true;
+ tree[length - 1].inputEnabled = false;
+ }
+
+ return menu;
+ }
+
+ public function pop():TreeMenuScreen {
+ (lastMenu = tree.pop(position)).parent = null;
+ length--;
+
+ menuChanged();
+ lastMenu.inputEnabled = false;
+ menu.inputEnabled = true;
+
+ return lastMenu;
+ }
+
+ public function remove(menu:TreeMenuScreen):TreeMenuScreen {
+ return if (menu == null) null; else removePosition(tree.indexOf(menu));
+ }
+
+ public function removePosition(position:Int):TreeMenuScreen {
+ if (position < 0 || position >= length) return null;
+
+ destroyLastMenu();
+ (lastMenu = tree.swapAndPop(position)).parent = null;
+
+ if (position == --length) {
+ menuChanged();
+ lastMenu.inputEnabled = false;
+ tree[length - 1].inputEnabled = true;
+ }
+ else {
+ updateMenuPositions(position, true);
+ updateLabels();
+ }
+
+ return lastMenu;
+ }
+
+ public function menuChanged() {
+ if (length == 0) exit();
+ else {
+ updateLabels();
+
+ if (menuChangeTween != null && menuChangeTween.active) menuChangeTween.cancel();
+
+ menuChangeTween = FlxTween.tween(FlxG.camera.scroll, {x: tree.last().x}, 1.5, {ease: menuTransitionEase, onComplete: (t) -> {
+ for (menu in tree) if (menu != null) menu.transitioning = false;
+ lastMenu.transitioning = false;
+ }});
+
+ for (menu in tree) if (menu != null) menu.transitioning = true;
+ lastMenu.transitioning = true;
}
}
- public function onMenuClose(m:OptionsScreen) {
- CoolUtil.playMenuSFX(CANCEL);
- }
-
- public function reloadLabels() {
- var t = "";
- for(o in optionsTree.members)
- t += '${o.name} > ';
- pathLabel.text = t;
-
- var idk:OptionsScreen = optionsTree.members.last();
- if (idk.members.length > 0) updateDesc(idk.members[idk.curSelected].desc);
- }
-
- public function updateDesc(moreTxt:String = '') {
- pathDesc.text = optionsTree.members.last().desc;
- if (moreTxt != null && moreTxt.length > 0) pathDesc.text += '\n' + moreTxt;
- pathBG.scale.set(FlxG.width, pathDesc.y + pathDesc.height + 2);
- pathBG.updateHitbox();
- }
-
- public function exit() {
- FlxG.switchState((lastState != null) ? Type.createInstance(lastState, []) : new MainMenuState());
- lastState = null;
- }
-
- var menuChangeTween:FlxTween;
- public override function update(elapsed:Float) {
+ override function update(elapsed:Float) {
super.update(elapsed);
- Framerate.offset.y = pathBG.height;
- // in case path gets so long it goes offscreen
- pathLabel.x = lerp(pathLabel.x, Math.max(0, FlxG.width - 4 - pathLabel.width), 0.125);
+ var i = 0, menu:TreeMenuScreen;
+ while (i < length) {
+ if ((menu = tree[i++]) == null || !menu.active || !menu.exists) continue;
+ if (i == length || menu.persistentUpdate || menu.transitioning) menu.update(elapsed);
+ }
+
+ if (lastMenu != null) {
+ if (lastMenu.transitioning) lastMenu.update(elapsed);
+ else destroyLastMenu();
+ }
+
+ // in case path gets so long it goes offscreen, ALTHOUGH this nevers happens anyway since we have set a expected width to the label.
+ //titleLabel.x = lerp(titleLabel.x, Math.max(0, FlxG.width - 4 - titleLabel.width), 0.125);
}
- public override function onResize(width:Int, height:Int) {
+ override function destroy() {
+ super.destroy();
+ destroyLastMenu();
+ }
+
+ override function onResize(width:Int, height:Int) {
super.onResize(width, height);
if (!UIState.resolutionAware) return;
if (width < FlxG.initialWidth || height < FlxG.initialHeight) {
- width = FlxG.initialWidth; height = FlxG.initialHeight;
+ width = FlxG.initialWidth;
+ height = FlxG.initialHeight;
}
- screenScroll = screenScroll; // Updating the cam position - Nex
-
- if (pathDesc != null && pathLabel != null)
- pathDesc.width = pathLabel.width = width - 8;
+ updateMenuPositions(true);
+ descLabel.width = titleLabel.width = (bgLabel.width = width) - 8;
}
- public static inline function menuTransitionEase(e:Float)
- return FlxEase.quintInOut(FlxEase.cubeOut(e));
+ public function reloadStrings() {
+ for (menu in tree) if (menu != null) menu.reloadStrings();
+ }
+
+ public function destroyLastMenu() {
+ lastMenu = destroyMenus ? FlxDestroyUtil.destroy(lastMenu) : null;
+ }
+
+ public function exit() {
+ if (exitCallback != null) return exitCallback(this);
+
+ FlxG.switchState(new MainMenuState());
+ }
+
+ public function updateAll(elapsed:Float) {
+ for (menu in tree) if (menu != null && menu.active && menu.exists) menu.update(elapsed);
+ }
+
+ public dynamic function menuTransitionEase(e:Float) return FlxEase.quintInOut(FlxEase.cubeOut(e));
}
-typedef OptionCategory = {
- var name:String;
- var desc:String;
- var state:OneOfThree, (name:String, desc:String) -> OptionsScreen>;
- var ?substate:OneOfThree, (name:String, desc:String) -> MusicBeatSubstate>;
- var ?suffix:String;
-}
+final class TreeMenuDrawer extends FlxBasic {
+ public var parent:TreeMenu;
+ public function new(parent:TreeMenu) {
+ this.parent = parent;
+ moves = false;
+ }
-typedef OptionTypeDef = {
- var type:Class;
- var args:Array;
-}
+ override function draw() {
+ var i = 0, menu:TreeMenuScreen;
+ while (i < parent.length) {
+ if ((menu = parent.tree[i++]) == null || !menu.active || !menu.exists) continue;
+ if (i == length || menu.persistentUpdate || menu.transitioning) menu.update(elapsed);
+ }
+
+ if (parent.lastMenu != null) parent.lastMenu.draw();
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/options/TreeMenuScreen.hx b/source/funkin/options/TreeMenuScreen.hx
new file mode 100644
index 00000000..7fa6e19f
--- /dev/null
+++ b/source/funkin/options/TreeMenuScreen.hx
@@ -0,0 +1,130 @@
+package funkin.options;
+
+import flixel.util.FlxSignal;
+import funkin.backend.system.Controls;
+import funkin.backend.TurboControls;
+import funkin.options.TreeMenu.ITreeOption;
+
+class TreeMenuScreen extends FlxSpriteGroup {
+ public var persistentUpdate:Bool = false;
+ public var persistentDraw:Bool = false;
+
+ public var onClose:FlxSignal = new FlxSignal();
+
+ public var parent:TreeMenu;
+ public var transitioning:Bool = false;
+ public var inputEnabled:Bool = false;
+ public var curSelected:Int = 0;
+ public var itemHeight:Float = 120;
+
+ public var name:String;
+ public var desc:String;
+ /**
+ * The prefix to add to the translations ids.
+ **/
+ public var prefix:String = "";
+
+ private var rawName(default, set):String;
+ private var rawDesc(default, set):String;
+
+ function set_rawName(v:String) {
+ if (rawName == (rawName = v)) return v;
+ name = TU.exists(rawName) ? TU.translate(rawName) : rawName;
+ return v;
+ }
+
+ function set_rawDesc(v:String) {
+ if (rawDesc == (rawDesc = v)) return v;
+ desc = TU.exists(rawDesc) ? TU.translate(rawDesc) : rawDesc;
+ return v;
+ }
+
+ public inline function getNameID(name):String return prefix + name + "-name";
+ public inline function getDescID(name):String return prefix + name + "-desc";
+
+ public var controls(get, never):Controls;
+ inline function get_controls():Controls return PlayerSettings.solo.controls;
+
+ var leftTurboControl:TurboControls = new TurboControls([Control.LEFT]);
+ var rightTurboControl:TurboControls = new TurboControls([Control.RIGHT]);
+ var upTurboControl:TurboControls = new TurboControls([Control.UP]);
+ var downTurboControl:TurboControls = new TurboControls([Control.DOWN]);
+ var turboBasics:Array;
+
+ var curOption:ITreeOption;
+ var _firstFrame:Bool = true;
+
+ public function new(name:String, desc:String, ?prefix:String, ?options:Array) {
+ super();
+
+ rawName = name;
+ rawDesc = desc;
+
+ turboBasics = [leftTurboControl, rightTurboControl, upTurboControl, downTurboControl];
+ }
+
+ public function reloadStrings() {
+ name = TU.exists(rawName) ? TU.translate(rawName) : rawName;
+ desc = TU.exists(rawDesc) ? TU.translate(rawDesc) : rawDesc;
+
+ for (object in members) if (object != null) object.reloadStrings();
+ }
+
+ override function update(elapsed:Float) {
+ super.update(elapsed);
+
+ if (_firstFrame) {
+ _firstFrame = false;
+ if (members[curSelected] is ITreeOption) (curOption = cast members[curSelected]:ITreeOption).selected = true;
+ }
+
+ if (inputEnabled) {
+ for (basic in turboBasics) basic.update(elapsed);
+ changeSelection((upTurboControl.activated ? -1 : 0) + (downTurboControl.activated ? 1 : 0) - FlxG.mouse.wheel);
+
+ if (i > 0 && curOption != null) {
+ if (controls.ACCEPT || FLxG.mouse.justPressed) curOption.select();
+ if (leftTurboControl.activated) curOption.changeSelection(-1);
+ if (rightTurboControl.activated) curOption.changeSelection(1);
+ }
+
+ if (controls.BACK || FlxG.mouse.justPressedRight) close();
+ }
+
+ var i:Int = 0, object:FlxSprite;
+ while (i < length) if ((object = members[i++]) != null) {
+ var h = object.height;
+ object.y = CoolUtil.fpsLerp(object.y, y + (FlxG.camera.height - h) * 0.5 + (i - curSelected) * itemHeight, 0.25);
+ object.x = x - 50 + Math.abs(Math.cos((object.y + (FlxG.camera.height + h) * 0.5 - FlxG.camera.scroll.y) / (FlxG.camera.height * 1.25) * Math.PI)) * 150;
+ }
+ }
+
+ public function close() {
+ onClose.dispatch();
+
+ if (parent == null) return destroy();
+ else parent.remove(this);
+
+ CoolUtil.playMenuSFX(CANCEL);
+ }
+
+ public function changeSelection(change:Int, force:Bool = false) {
+ if (length == 0 || (change == 0 && !force)) return;
+
+ curSelected = FlxMath.wrap(curSelected + change, 0, members.length - 1);
+ if (members[curSelected] is ITreeOption) (curOption = cast members[curSelected]:ITreeOption).selected = true;
+ updateMenuDesc();
+
+ CoolUtil.playMenuSFX(SCROLL);
+ }
+
+ public function updateMenuDesc(?customTxt:String) {
+ if (parent != null) parent.updateDesc(customTxt);
+ }
+
+ override function destroy() {
+ super.destroy();
+
+ for (basic in turboBasics) basic.destroy();
+ }
+}
\ No newline at end of file
diff --git a/source/funkin/options/categories/AppearanceOptions.hx b/source/funkin/options/categories/AppearanceOptions.hx
index fd364bdd..0131a73d 100644
--- a/source/funkin/options/categories/AppearanceOptions.hx
+++ b/source/funkin/options/categories/AppearanceOptions.hx
@@ -1,8 +1,10 @@
package funkin.options.categories;
-class AppearanceOptions extends OptionsScreen {
- public override function new(title:String, desc:String) {
- super(title, desc, "AppearanceOptions.");
+class AppearanceOptions extends TreeMenuScreen {
+ public override function new() {
+ super('optionsTree.appearance-name', 'optionsTree.appearance-desc', "AppearanceOptions.");
+
+ /*
add(new NumOption(
getName("framerate"),
getDesc("framerate"),
@@ -43,13 +45,12 @@ class AppearanceOptions extends OptionsScreen {
"gpuOnlyBitmaps"));
}
#end
+ */
}
- private function __changeFPS(change:Float) {
- // if statement cause of the flixel warning
- if(FlxG.updateFramerate < Std.int(change))
- FlxG.drawFramerate = FlxG.updateFramerate = Std.int(change);
- else
- FlxG.updateFramerate = FlxG.drawFramerate = Std.int(change);
+ private function __changeFPS(value:Float) {
+ var framerate = Math.floor(value);
+ if (FlxG.updateFramerate < framerate) FlxG.drawFramerate = FlxG.updateFramerate = framerate;
+ else FlxG.updateFramerate = FlxG.drawFramerate = framerate;
}
}
\ No newline at end of file
diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx
index 4e1f8a7e..3064a588 100644
--- a/source/funkin/options/categories/GameplayOptions.hx
+++ b/source/funkin/options/categories/GameplayOptions.hx
@@ -30,13 +30,13 @@ class GameplayOptions extends OptionsScreen {
getName("autoPause"),
getDesc("autoPause"),
"autoPause"));
- add(new MeterOption(
+ /*add(new MeterOption(
getName("volumeSFX"),
getDesc("volumeSFX"),
0, // minimum
1, // maximum
0.1, // change
- "volumeSFX"));
+ "volumeSFX"));*/
add(offsetSetting = new NumOption(
getName("songOffset"),
getDesc("songOffset"),
diff --git a/source/funkin/options/categories/import.hx b/source/funkin/options/categories/import.hx
index 49bf382e..cb4b5c3a 100644
--- a/source/funkin/options/categories/import.hx
+++ b/source/funkin/options/categories/import.hx
@@ -1,5 +1,7 @@
#if !macro
package funkin.options.categories;
+import funkin.options.TreeMenu;
+import funkin.options.TreeMenuScreen;
import funkin.options.type.*;
#end
\ No newline at end of file
diff --git a/source/funkin/options/type/ArrayOption.hx b/source/funkin/options/type/ArrayOption.hx
index 885d0335..81184aeb 100644
--- a/source/funkin/options/type/ArrayOption.hx
+++ b/source/funkin/options/type/ArrayOption.hx
@@ -1,88 +1,57 @@
package funkin.options.type;
-/**
- * Option type that allows you to select an array of options.
-**/
class ArrayOption extends OptionType {
public var selectCallback:String->Void;
- private var __text:Alphabet;
- private var __selectiontext:Alphabet;
-
public var options:Array;
public var displayOptions:Array;
-
public var currentSelection:Int;
- var optionName:String;
-
public var parent:Dynamic;
+ public var optionName:String;
- private var rawText(default, set):String;
+ var __text:Alphabet;
+ var __selectionText:Alphabet;
- public var text(get, set):String;
- private function get_text() {return __text.text;}
- private function set_text(v:String) {return __text.text = v;}
-
- public function new(text:String, desc:String, options:Array, displayOptions:Array, optionName:String, ?selectCallback:String->Void = null, ?parent:Dynamic) {
- super(desc);
+ public function new(text:String, desc:String, options:Array, displayOptions:Array, ?optionName:String, ?selectCallback:String->Void = null, ?parent:Dynamic) {
+ super(text, desc);
this.selectCallback = selectCallback;
this.displayOptions = displayOptions;
this.options = options;
- if (parent == null)
- parent = Options;
-
- this.parent = parent;
-
- var fieldValue = Reflect.field(parent, optionName);
- if(fieldValue != null)
- this.currentSelection = CoolUtil.maxInt(0, options.indexOf(fieldValue));
-
+ this.parent = parent = parent != null ? parent : Options;
this.optionName = optionName;
- add(__text = new Alphabet(100, 20, text, "bold"));
- add(__selectiontext = new Alphabet(__text.width + 120, 20, formatTextOption(), "normal"));
+ var fieldValue = Reflect.field(parent, optionName);
+ if (fieldValue != null) currentSelection = CoolUtil.maxInt(0, options.indexOf(fieldValue));
- rawText = text;
+ add(__text = new Alphabet(20, 0, text, 'bold'));
+ add(__selectionText = new Alphabet(__text.x + __text.width + 12, 0, formatTextOption(), 'bold'));
}
override function reloadStrings() {
super.reloadStrings();
- this.rawText = rawText;
- }
-
- function set_rawText(v:String) {
- rawText = v;
- __text.text = TU.exists(rawText) ? TU.translate(rawText) : rawText;
- return v;
- }
-
- public override function draw() {
- super.draw();
- }
-
- public override function onChangeSelection(change:Float):Void
- {
- if(currentSelection <= 0 && change == -1 || currentSelection >= options.length - 1 && change == 1) return;
- currentSelection += Math.round(change);
+ __selectiontext.x = __text.x + __text.width + 12;
__selectiontext.text = formatTextOption();
- Reflect.setField(parent, optionName, options[currentSelection]);
- if(selectCallback != null)
- selectCallback(options[currentSelection]);
}
- private function formatTextOption() {
- var currentOptionString = ": ";
- if((currentSelection > 0))
- currentOptionString += "< ";
- else
- currentOptionString += " ";
+ override function changeSelection(change:Int) {
+ currentSelection = FlxMath.wrap(currentSelection + change, 0, options.length - 1);
+ __selectionText.text = formatTextOption();
+ CoolUtil.playMenuSFX(SCROLL);
- currentOptionString += displayOptions[currentSelection];
+ if (selectCallback != null) selectCallback(options[currentSelection]);
+ }
- if(!(currentSelection >= options.length - 1))
- currentOptionString += " >";
+ function formatTextOption() {
+ var s = ": ";
- return currentOptionString;
+ if (currentSelection > 0) s += "< ";
+ else s += " ";
+
+ s += TU.exists(displayOptions[currentSelection]) ? TU.translate(displayOptions[currentSelection]) : displayOptions[currentSelection];
+
+ if (currentSelection < options.length - 1) s += " >";
+
+ return s;
}
}
\ No newline at end of file
diff --git a/source/funkin/options/type/OptionType.hx b/source/funkin/options/type/OptionType.hx
index 44cd94e1..7429a64a 100644
--- a/source/funkin/options/type/OptionType.hx
+++ b/source/funkin/options/type/OptionType.hx
@@ -2,40 +2,48 @@ package funkin.options.type;
import flixel.group.FlxSpriteGroup;
import funkin.backend.system.Controls;
+import funkin.options.TreeMenu.ITreeOption;
/**
* Base class for all option types.
* Used in OptionsMenu.
**/
-class OptionType extends FlxSpriteGroup {
- public var controls(get, never):Controls;
+class OptionType extends FlxSpriteGroup implements ITreeOption {
public var selected:Bool = false;
+
+ public var text(default, set):String;
+ public var rawText(default, set):String;
public var desc:String;
public var rawDesc(default, set):String;
- public function new(desc:String) {
+ public function new(text:String, desc:String) {
super();
- rawDesc = desc;
+ reloadStrings();
+ }
+
+ function set_text(v:String) return text = v;
+ function set_rawText(v:String) {
+ rawText = v;
+ text = TU.exists(rawText) ? TU.translate(rawText) : rawText;
+ return v;
}
function set_rawDesc(v:String) {
rawDesc = v;
- this.desc = TU.exists(rawDesc) ? TU.translate(rawDesc) : rawDesc;
+ desc = TU.exists(rawDesc) ? TU.translate(rawDesc) : rawDesc;
return v;
}
public function reloadStrings() {
- this.rawDesc = rawDesc;
+ rawText = rawText;
+ rawDesc = rawDesc;
}
- private function get_controls() {return PlayerSettings.solo.controls;}
-
- public function onSelect() {}
-
- public function onChangeSelection(change:Float) {}
-
public override function update(elapsed:Float) {
super.update(elapsed);
alpha = (selected ? 1 : 0.6);
}
+
+ public function changeSelection(change:Int) {}
+ public function select() {}
}
\ No newline at end of file
diff --git a/source/funkin/options/type/SliderOption.hx b/source/funkin/options/type/SliderOption.hx
new file mode 100644
index 00000000..37f4f4fc
--- /dev/null
+++ b/source/funkin/options/type/SliderOption.hx
@@ -0,0 +1,35 @@
+package funkin.options.type;
+
+class SliderOption extends OptionType {
+ public var selectCallback:Float->Void;
+
+ public var min:Float;
+ public var max:Float;
+ public var segmentVal:Float;
+
+ public var currentSelection:Float;
+
+ public var parent:Dynamic;
+
+ //public var barWidth(get, set):Int;
+
+ public var text(get, set):String;
+ private function get_text() {return __text.text;}
+ private function set_text(v:String) {return __text.text = v;}
+ private var rawText(default, set):String;
+
+ var __text:Alphabet;
+ var optionName:String;
+
+ public function new(text:String, desc:String, min:Float, max:Float, segmentVal:Float, optionName:String, barWidth:Int = 520,
+ ?selectCallback:Float->Void = null, ?parent:Dynamic)
+ {
+ super(desc);
+ }
+
+ function set_rawText(v:String) {
+ rawText = v;
+ __text.text = TU.exists(rawText) ? TU.translate(rawText) : rawText;
+ return v;
+ }
+}
\ No newline at end of file