diff --git a/assets/images/editors/bgs/default.png b/assets/images/editors/bgs/default.png
new file mode 100644
index 00000000..1b99f9a5
Binary files /dev/null and b/assets/images/editors/bgs/default.png differ
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..81a9dd2b
--- /dev/null
+++ b/assets/images/menus/options/slider.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/languages/en/Editors.xml b/assets/languages/en/Editors.xml
index 3fe1239c..a0df9dc4 100644
--- a/assets/languages/en/Editors.xml
+++ b/assets/languages/en/Editors.xml
@@ -46,7 +46,9 @@
Press ACCEPT to edit the chart for the selected difficulty.
Select a difficulty to continue.
New Difficulty
+ Press ACCEPT to create a new chart difficulty.
New Song
+ Press ACCEPT to create a new song.
@@ -437,6 +439,11 @@
Creating New Character
Character Name $* Required$
Character Image File $* Required$
+
+
+ Creating Character: Error!
+ The character you are trying to create already exists, if you would like to override it delete the character first!
+
@@ -444,6 +451,7 @@
Press ACCEPT to edit this typeface.
New Typeface
+ Press ACCEPT to create a new typeface.
Select a typeface to edit
@@ -556,6 +564,11 @@
Stage Info
Stage Name
Stage Path
+
+
+ Creating Stage: Error!
+ The stage you are trying to create alreadly exists, if you would like to override it delete the stage first!
+
diff --git a/assets/languages/en/Options.xml b/assets/languages/en/Options.xml
index 2b5a6c00..7145f793 100644
--- a/assets/languages/en/Options.xml
+++ b/assets/languages/en/Options.xml
@@ -12,10 +12,11 @@
Gameplay
Change Gameplay options such as Downscroll, Scroll Speed, Naughtyness...
- Change more advanced options such as Window Size, Streaming Audio...
+ Change more advanced options such as Hit Window Size, Streaming Audio...
Appearance
Change Appearance options such as Flashing menus...
+ Change more advanced options such as VRAM-Only Sprites, Antialiasing, Shaders
Language
Change the language that the engine uses.
@@ -23,6 +24,9 @@
Miscellaneous
Use this menu to reset save data or engine settings.
+ Debug Options
+ Debug Options for Developer Mode.
+
@@ -62,8 +66,8 @@
Framerate
Pretty self explanatory, isn't it?
- Antialiasing
- If unchecked, will disable antialiasing on every sprite. Can boost performances at the cost of sharper, more pixely sprites.
+
+
Colored Healthbar
If unchecked, the game will use the original red/green health bar from the week 6 fnf game.
@@ -71,17 +75,25 @@
Pixel Perfect Effect
If checked, Week 6 will have a pixel perfect effect to it enabled, aligning every pixel on the screen.
- Gameplay Shaders
- If unchecked, gameplay shaders (visual effects like Thorns's Chromatic Aberration) wont be loaded; this may be helpful on weak devices.
+
+ Quality
+ Quality modes to choose, "LOW" = Disable every heavy visual effects as it can get to smoothen the gameplay, "HIGH" = Enable all of the visual effects, with the cost of performance, "CUSTOM" = Allow of custom options that is available in Advanced.
+ LOW
+ HIGH
+ CUSTOM
-
-
+ Antialiasing
+ If unchecked, will disable antialiasing on every sprite. Can boost performances at the cost of sharper, more pixely sprites.
- Low Memory Mode
- If checked, will disable certain background elements in stages to reduce memory usage.
+ Low Memory Mode
+ If checked, will disable certain background elements in stages to reduce memory usage.
- VRAM-Only Sprites
- If checked, will only store the bitmaps in the GPU, freeing a LOT of memory (EXPERIMENTAL). Turning this off will consume a lot of memory, especially on bigger sprites. If you aren't sure, leave this on.
+ Gameplay Shaders
+ If unchecked, gameplay shaders (visual effects like Thorns's Chromatic Aberration) wont be loaded; this may be helpful on weak devices.
+
+ VRAM-Only Sprites
+ If checked, will only store the bitmaps in the GPU, freeing a LOT of memory (EXPERIMENTAL). Turning this off will consume a lot of memory, especially on bigger sprites. If you aren't sure, leave this on.
+
@@ -100,6 +112,9 @@
Auto Pause
If checked, switching windows will pause the game.
+ Music Volume
+ Changes only the volume of the music instead of everything.
+
SFX Volume
Changes only the volume of the SFX instead of everything.
@@ -137,6 +152,9 @@
Developer Mode
If checked, you will be able to access developer features like certain keybinds, editors, developer options, state reloads, console and more.
+ Show Configs Warning
+ If checked, a popup at the start of the game will appear if the current mod doesn't have a mod configuration file
+
Reset Save Data
Select this option to reset save data. This will remove all of your highscores.
diff --git a/assets/languages/pl/Options.xml b/assets/languages/pl/Options.xml
index 0415b455..68526dde 100644
--- a/assets/languages/pl/Options.xml
+++ b/assets/languages/pl/Options.xml
@@ -62,26 +62,28 @@
Klatki
Ilość Klatek na sekundę ktore gra renderuje
- Wygładzanie
- Jeśli wyłączone, Wygładzanie nie będzie widoczne na Sprite'ach. Może poprawić wydajność na koszt ostrzejszego, pikselowatego wyglądu Sprite'ów.
-
Kolorowe Zdrowie
Jeśli wyłączone, Pasek zdrowia będzie tylko używał czerwonego i zielonego koloru, takiego jak z oryginalnej gry.
Perfekcyjne Piksele
Jeśli włączone, Tydzień 6 będzie miał efekt perfekcyjnych pikseli, ustawiający każdy piksel na ekranie.
- Shadery Rozgrywki
- Jeśli wyłączone, Shadery Rozgrywki (efekty wizualne jak Aberacja Chromatyczna z Thorns) nie będą ładowane; może być to pomocne na słabszych urządzeniach.
-
- Niskie zużycie pamięci
+
+ Wygładzanie
+ Jeśli wyłączone, Wygładzanie nie będzie widoczne na Sprite'ach. Może poprawić wydajność na koszt ostrzejszego, pikselowatego wyglądu Sprite'ów.
+
+ Niskie zużycie pamięci
Jeśli włączone, usunie niektóre elementy tła, co użyje mniej pamięci.
- Sprite'y w VRAM
- Jeśli włączone, utrzyma obrazy tylko w GPU, uwalniając więcej pamięci (EKSPERYMENTALNE). Wyłaczenie tego użyje o wiele więcej pamięci, szczególnie przy większych duszkach. Zostaw włączone w wypadku wątpliwości.
+ Shadery Rozgrywki
+ Jeśli wyłączone, Shadery Rozgrywki (efekty wizualne jak Aberacja Chromatyczna z Thorns) nie będą ładowane; może być to pomocne na słabszych urządzeniach.
+
+ Sprite'y w VRAM
+ Jeśli włączone, utrzyma obrazy tylko w GPU, uwalniając więcej pamięci (EKSPERYMENTALNE). Wyłaczenie tego użyje o wiele więcej pamięci, szczególnie przy większych duszkach. Zostaw włączone w wypadku wątpliwości.
+
@@ -112,7 +114,7 @@
Strumieniowana Muzyka
Jeśli włączone, tylko muzyka będzie strumieniować audio, uwalniając więcej pamięci, na możliwy koszt większego użycia CPU. (EKSPERYMENTALNE)
-If checked, only musics will have streamed audio, ALSO freeing a LOT of memory with the downside of higher cpu usage if more audio are being streamed at once (EXPERIMENTAL). Turning this off will consume a lot of memory, especially on longer songs. If you aren't sure, leave this on.
+
Strumieniowane Wokale
Jeśli włączone, tylko wokale będą strumieniować audio, uwalniając więcej pamięci, na możliwy koszt większego użycia CPU. (EKSPERYMENTALNE)
diff --git a/source/flixel/sound/FlxSound.hx b/source/flixel/sound/FlxSound.hx
index 146cdf1a..eb39c4ac 100644
--- a/source/flixel/sound/FlxSound.hx
+++ b/source/flixel/sound/FlxSound.hx
@@ -312,6 +312,7 @@ class FlxSound extends FlxBasic {
_source = null;
_sound = null;
+ if (autoDestroy) persist = false;
reset();
}
else if (_channel != null && _channel.__isValid) {
diff --git a/source/funkin/backend/TurboControls.hx b/source/funkin/backend/TurboControls.hx
new file mode 100644
index 00000000..b8bedf22
--- /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 != null ? 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..5f6f8f98 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/system/MainState.hx b/source/funkin/backend/system/MainState.hx
index ba58479c..0d415f26 100644
--- a/source/funkin/backend/system/MainState.hx
+++ b/source/funkin/backend/system/MainState.hx
@@ -122,7 +122,7 @@ class MainState extends FlxState {
CoolUtil.safeAddAttributes('./.temp/', NativeAPI.FileAttribute.HIDDEN);
#end
- if (Options.devMode) {
+ if (Options.devMode && Options.allowConfigWarning) {
var lib:ModsFolderLibrary;
for (e in Paths.assetsTree.libraries) {
@:privateAccess if (!(e is openfl.utils.AssetLibrary) || !((lib = cast cast(e, openfl.utils.AssetLibrary).__proxy) is ModsFolderLibrary)) continue;
diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx
index d24e153a..175e0eb0 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';
@@ -528,7 +528,7 @@ final class CoolUtil
case UNCHECKED: 'checkboxUnchecked';
case WARNING: 'warningMenu';
default: 'scroll';
- }), volume);
+ }), volume * Options.volumeSFX);
}
/**
diff --git a/source/funkin/editors/EditorTreeMenu.hx b/source/funkin/editors/EditorTreeMenu.hx
index 7bf5a0fb..1a7a0e37 100644
--- a/source/funkin/editors/EditorTreeMenu.hx
+++ b/source/funkin/editors/EditorTreeMenu.hx
@@ -1,26 +1,25 @@
package funkin.editors;
import flixel.addons.display.FlxBackdrop;
-import funkin.menus.MainMenuState;
-import funkin.options.*;
+import funkin.options.type.OptionType;
-class EditorTreeMenu extends TreeMenu {
+class EditorTreeMenu extends funkin.options.TreeMenu {
public var bg:FlxBackdrop;
public var bgType:String = "default";
public var bgMovement:FlxPoint = new FlxPoint();
- public override function create() {
+ override function create() {
super.create();
-
UIState.setResolutionAware();
-
FlxG.camera.fade(0xFF000000, 0.5, true);
+ }
- bg = new FlxBackdrop();
+ override function createPost() {
+ insert(0, bg = new FlxBackdrop());
bg.loadGraphic(Paths.image('editors/bgs/${bgType}'));
bg.antialiasing = true;
setBackgroundRotation(-5);
- add(bg);
+ super.createPost();
}
public inline function setBackgroundRotation(rotation:Float) {
@@ -28,7 +27,47 @@ class EditorTreeMenu extends TreeMenu {
bg.velocity.set(85, 0).degrees = bg.rotation;
}
- public override function exit() {
- FlxG.switchState(new MainMenuState());
+ override function exit() {
+ FlxG.switchState(new funkin.menus.MainMenuState());
+ }
+
+ override function update(elapsed:Float) {
+ super.update(elapsed);
+
+ bg.colorTransform.redOffset = lerp(bg.colorTransform.redOffset, 0, 0.0625);
+ bg.colorTransform.greenOffset = lerp(bg.colorTransform.greenOffset, 0, 0.0625);
+ bg.colorTransform.blueOffset = lerp(bg.colorTransform.blueOffset, 0, 0.0625);
+ bg.colorTransform.redMultiplier = lerp(bg.colorTransform.redMultiplier, 1, 0.0625);
+ bg.colorTransform.greenMultiplier = lerp(bg.colorTransform.greenMultiplier, 1, 0.0625);
+ bg.colorTransform.blueMultiplier = lerp(bg.colorTransform.blueMultiplier, 1, 0.0625);
+ }
+
+ override function menuChanged() @:privateAccess {
+ super.menuChanged();
+ if (previousMenus.length > 0) return; // selected a sub-menu
+
+ var prev = tree[tree.length - 2];
+ if (prev == null || prev.curOption == null || !(prev.curOption is OptionType)) return;
+
+ // small flashbang
+ var color = cast(prev.curOption, OptionType).editorFlashColor;
+ bg.colorTransform.redOffset = 0.25 * color.red;
+ bg.colorTransform.greenOffset = 0.25 * color.green;
+ bg.colorTransform.blueOffset = 0.25 * color.blue;
+ bg.colorTransform.redMultiplier = FlxMath.lerp(1, color.redFloat, 0.25);
+ bg.colorTransform.greenMultiplier = FlxMath.lerp(1, color.greenFloat, 0.25);
+ bg.colorTransform.blueMultiplier = FlxMath.lerp(1, color.blueFloat, 0.25);
+ }
+}
+
+class EditorTreeMenuScreen extends funkin.options.TreeMenuScreen {
+ public function new(name:String, desc:String, ?prefix:String, ?objects:Array,
+ ?newButton:String, ?newButtonDesc:String, ?newCallback:Void->Void)
+ {
+ super(name, desc, prefix, objects);
+ if (newCallback != null) {
+ insert(0, new funkin.options.type.NewOption(getID(newButton), getID(newButtonDesc), newCallback));
+ curSelected = 1;
+ }
}
}
\ No newline at end of file
diff --git a/source/funkin/editors/alphabet/AlphabetSelection.hx b/source/funkin/editors/alphabet/AlphabetSelection.hx
index 1b8155ee..bb274c8f 100644
--- a/source/funkin/editors/alphabet/AlphabetSelection.hx
+++ b/source/funkin/editors/alphabet/AlphabetSelection.hx
@@ -1,51 +1,35 @@
package funkin.editors.alphabet;
+import haxe.io.Path;
import haxe.xml.Access;
import funkin.game.Character;
-import funkin.options.OptionsScreen;
-import funkin.options.type.IconOption;
+import funkin.editors.EditorTreeMenu;
import funkin.options.type.NewOption;
import funkin.options.type.TextOption;
import funkin.options.type.OptionType;
-import haxe.io.Path;
-class AlphabetSelection extends EditorTreeMenu
-{
- inline function translate(id:String, ?args:Array)
- return TU.translate("editor.alphabet.selection." + id, args);
-
- public override function create()
- {
- bgType = "charter";
+class AlphabetSelection extends EditorTreeMenu {
+ override function create() {
super.create();
+ DiscordUtil.call("onEditorTreeLoaded", ["Alphabet Editor"]);
+ addMenu(new AlphabetSelectionScreen());
+ }
+}
+
+class AlphabetSelectionScreen extends EditorTreeMenuScreen {
+ public function new() {
+ super('editor.alphabet.name', 'editor.alphabet.selection.desc', 'editor.alphabet.selection.', 'newTypeface', 'newTypefaceDesc', () -> {
+ parent.openSubState(new UIWarningSubstate(translate('warnings.notImplemented-title'), translate('warnings.notImplemented-body'), [
+ {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}}
+ ]));
+ });
var modsList:Array = [];
- for(file in Paths.getFolderContent('data/alphabet/', true, BOTH)) // mods ? MODS : BOTH
+ for (file in Paths.getFolderContent('data/alphabet/', true, BOTH)) // mods ? MODS : BOTH
if (Path.extension(file) == "xml") modsList.push(CoolUtil.getFilename(file));
- var list:Array = [
- for (typeface in modsList)
- new AlphabetIconOption(typeface, translate("acceptTypeface"), typeface, function() {
- FlxG.switchState(new AlphabetEditor(typeface));
- })
- ];
-
- var newChar = translate("newTypeface");
- list.insert(0, new NewOption(newChar, newChar, function() {
- openSubState(new UIWarningSubstate(translate("warnings.notImplemented-title"), translate("warnings.notImplemented-body"), [
- {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}}
- ]));
- }));
-
- main = new OptionsScreen(TU.translate("editor.alphabet.name"), translate("desc"), list);
-
- DiscordUtil.call("onEditorTreeLoaded", ["Alphabet Editor"]);
- }
-
- override function createPost() {
- super.createPost();
-
- main.changeSelection(1);
+ for (typeface in modsList)
+ add(new AlphabetIconOption(typeface, getID('acceptTypeface'), typeface, () -> FlxG.switchState(new AlphabetEditor(typeface))));
}
}
@@ -57,7 +41,7 @@ class AlphabetIconOption extends TextOption {
var xml = Xml.parse(Assets.getText(Paths.xml('alphabet/$typeface'))).firstElement();
var spritesheet = null;
- for(node in xml.elements()) {
+ for (node in xml.elements()) {
if (node.nodeName == "spritesheet") {
spritesheet = node.firstChild().nodeValue;
break;
@@ -73,17 +57,19 @@ class AlphabetIconOption extends TextOption {
iconSpr.frames = Paths.getFrames(spritesheet);
iconSpr.antialiasing = true;
var frameToUse = iconSpr.frames.frames[0];
- for(frame in iconSpr.frames.frames) {
+ for (frame in iconSpr.frames.frames) {
if (frame.name.toUpperCase().startsWith("A")) {
frameToUse = frame;
break;
}
}
iconSpr.frame = frameToUse;
- if(useColorOffsets) {
+ if (useColorOffsets) {
iconSpr.colorTransform.color = -1;
}
iconSpr.setPosition(90 - iconSpr.width - 20, (__text.height - iconSpr.height) / 2);
add(iconSpr);
+
+ __text.x = 100;
}
}
\ No newline at end of file
diff --git a/source/funkin/editors/character/CharacterSelection.hx b/source/funkin/editors/character/CharacterSelection.hx
index 9f009ccb..1b9d2d70 100644
--- a/source/funkin/editors/character/CharacterSelection.hx
+++ b/source/funkin/editors/character/CharacterSelection.hx
@@ -1,81 +1,69 @@
package funkin.editors.character;
import haxe.xml.Printer;
-import funkin.editors.ui.UIImageExplorer.ImageSaveData;
import funkin.game.Character;
-import funkin.options.OptionsScreen;
+import funkin.editors.ui.UIImageExplorer.ImageSaveData;
+import funkin.editors.EditorTreeMenu;
import funkin.options.type.IconOption;
import funkin.options.type.NewOption;
import funkin.options.type.TextOption;
import funkin.options.type.OptionType;
-import funkin.backend.assets.ModsFolder;
-class CharacterSelection extends EditorTreeMenu
-{
- inline function translate(id:String, ?args:Array)
- return TU.translate("characterSelection." + id, args);
+class CharacterSelection extends EditorTreeMenu {
+ override function create() {
+ super.create();
+ DiscordUtil.call("onEditorTreeLoaded", ["Character Editor"]);
+ addMenu(new CharacterSelectionScreen());
+ }
+}
+class CharacterSelectionScreen extends EditorTreeMenuScreen {
public var modsList:Array = [];
- public override function create()
- {
- bgType = "charter";
- super.create();
+ public function new() {
+ super('editor.character.name', 'characterSelection.desc', 'characterSelection.', 'newCharacter', 'newCharacterDesc', () -> {
+ parent.openSubState(new CharacterCreationScreen(createCharacter));
+ });
var isMods:Bool = true;
modsList = Character.getList(true, true);
- if(modsList.length == 0) {
+ if (modsList.length == 0) {
modsList = Character.getList(false, true);
isMods = false;
}
- function generateList(modsList:Array, isMods:Bool, folderPath:String = ""):Array {
- var list:Array = [];
+ function generateList(modsList:Array, isMods:Bool, folderPath:String = ""):Array {
+ var list:Array = [];
- for(char in modsList) {
- if(char.endsWith("/")) {
+ for (char in modsList) {
+ if (char.endsWith("/")) {
var folderName = CoolUtil.getFilename(char.substr(0, char.length-1));
- list.push(new TextOption(folderName + " >", translate("acceptFolder"), function() {
+ list.push(new TextOption(folderName, getID('acceptFolder'), ' >', () -> {
var newModsList = Character.getList(isMods, true, char);
- var newList:Array = generateList(newModsList, isMods, folderPath + folderName + "/");
- optionsTree.add(new OptionsScreen(folderPath + folderName, translate("desc-folder", [folderPath + folderName + "/"]), newList));
+ var newList:Array = generateList(newModsList, isMods, folderPath + folderName + "/");
+ parent.addMenu(new EditorTreeMenuScreen(folderPath + folderName, translate('desc-folder', [folderPath + folderName + "/"]), newList));
+ }));
+ }
+ else {
+ list.push(new IconOption(char, getID('acceptCharacter'), Character.getIconFromCharName(folderPath + char, char), () -> {
+ FlxG.switchState(new CharacterEditor(folderPath + char));
}));
- } else {
- list.push(new IconOption(char, translate("acceptCharacter"), Character.getIconFromCharName(folderPath + char, char),
- function() {
- FlxG.switchState(new CharacterEditor(folderPath + char));
- })
- );
}
}
- list.insert(0, new NewOption(translate("newCharacter"), translate("newCharacterDesc"), function() {
- openSubState(new CharacterCreationScreen(createCharacter));
- }));
-
return list;
}
- var list = generateList(modsList, isMods);
-
- main = new OptionsScreen(TU.translate("editor.character.name"), translate("desc"), list);
-
- DiscordUtil.call("onEditorTreeLoaded", ["Character Editor"]);
- }
-
- override function createPost() {
- super.createPost();
-
- main.changeSelection(1);
+ for (o in generateList(modsList, isMods)) add(o);
}
public function createCharacter(name:String, imageSaveData:ImageSaveData, xml:Xml) {
var characterAlreadyExists:Bool = modsList.contains(name);
if (characterAlreadyExists) {
- openSubState(new UIWarningSubstate("Creating Character: Error!", "The character you are trying to create already exists, if you would like to override it delete the character first!", [
- {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
+ parent.openSubState(new UIWarningSubstate(TU.translate('characterCreationScreen.warning.char-exists-title'), TU.translate('characterCreationScreen.warning.char-exists-body'), [
+ {label: TU.translate('editor.ok'), color: 0xFFFF0000, onClick: (t) -> {}}
]));
return;
}
@@ -88,11 +76,10 @@ class CharacterSelection extends EditorTreeMenu
UIImageExplorer.saveFilesGlobal(imageSaveData, '${Paths.getAssetsRoot()}/images/characters');
// Add to Menu >:D
- var option:IconOption = new IconOption(name, "Press ACCEPT to edit this character.", Character.getIconFromCharName(name),
- function() {
- FlxG.switchState(new CharacterEditor(name));
- }
- );
- optionsTree.members[optionsTree.members.length-1].insert(1, option);
+ var option:IconOption = new IconOption(name, getID('acceptCharacter'), Character.getIconFromCharName(name), () -> {
+ FlxG.switchState(new CharacterEditor(name));
+ });
+
+ insert(1, option);
}
}
\ No newline at end of file
diff --git a/source/funkin/editors/charter/CharterSelection.hx b/source/funkin/editors/charter/CharterSelection.hx
index 467817d7..85776f44 100644
--- a/source/funkin/editors/charter/CharterSelection.hx
+++ b/source/funkin/editors/charter/CharterSelection.hx
@@ -1,118 +1,83 @@
package funkin.editors.charter;
+import flixel.util.FlxColor;
import funkin.backend.chart.Chart;
-import funkin.backend.chart.ChartData.ChartMetaData;
import funkin.backend.chart.ChartData;
-import funkin.backend.system.framerate.Framerate;
-import funkin.editors.EditorTreeMenu;
import funkin.editors.charter.SongCreationScreen.SongCreationData;
+import funkin.editors.EditorTreeMenu;
import funkin.menus.FreeplayState.FreeplaySonglist;
-import funkin.options.*;
import funkin.options.type.*;
-import funkin.options.type.NewOption;
import haxe.Json;
using StringTools;
class CharterSelection extends EditorTreeMenu {
- public var freeplayList:FreeplaySonglist;
- public var curSong:ChartMetaData;
- public override function create() {
- bgType = "charter";
-
+ override function create() {
super.create();
+ DiscordUtil.call("onEditorTreeLoaded", ["Chart Editor"]);
+ addMenu(new CharterSelectionScreen());
+ bgType = 'charter';
+ }
+}
- Framerate.offset.y = 60;
+class CharterSelectionScreen extends EditorTreeMenuScreen {
+ public var freeplayList:FreeplaySonglist;
+ public var songList:Array = [];
+ public var curSong:ChartMetaData;
+ inline public function makeChartOption(d:String, name:String):TextOption {
+ return new TextOption(d, getID('acceptDifficulty'), () -> FlxG.switchState(new Charter(name, d)));
+ }
+
+ public function makeSongOption(s:ChartMetaData):IconOption {
+ songList.push(s.name.toLowerCase());
+
+ var opt = new IconOption(s.name, getID('acceptSong'), s.icon, () -> {
+ curSong = s;
+
+ var screen = new EditorTreeMenuScreen(s.name, getID('selectDifficulty'), [
+ for (d in s.difficulties) if (d != '') makeChartOption(d, s.name)
+ ]);
+
+ #if sys
+ screen.insert(0, new NewOption(getID('newDifficulty'), getID('newDifficultyDesc'), () -> {
+ parent.openSubState(new ChartCreationScreen(saveChart));
+ }));
+ screen.curSelected = 1;
+ #end
+
+ parent.addMenu(screen);
+ });
+ opt.suffix = ' >';
+ opt.editorFlashColor = s.color.getDefault(FlxColor.WHITE);
+
+ return opt;
+ }
+
+ public function new() {
+ super('editor.chart.name', 'charterSelection.desc', 'charterSelection.', 'newSong', 'newSongDesc', () -> {
+ parent.openSubState(new SongCreationScreen(saveSong));
+ });
freeplayList = FreeplaySonglist.get(false);
- var list:Array = [
- for(s in freeplayList.songs) new EditorIconOption(s.name, TU.translate("charterSelection.acceptSong"), s.icon, function() {
- curSong = s;
- var list:Array = [
- for(d in s.difficulties) if (d != "")
- new TextOption(d, TU.translate("charterSelection.acceptDifficulty"), function() {
- FlxG.switchState(new Charter(s.name, d));
- })
- ];
- #if sys
- var newDiff = TU.translate("charterSelection.newDifficulty");
- list.push(new NewOption(newDiff, newDiff, function() {
- FlxG.state.openSubState(new ChartCreationScreen(saveChart));
- }));
- #end
- optionsTree.add(new OptionsScreen(s.name, TU.translate("charterSelection.selectDifficulty"), list));
- }, s.color.getDefault(0xFFFFFFFF))
- ];
-
- #if sys
- var newSong = TU.translate("charterSelection.newSong");
- list.insert(0, new NewOption(newSong, newSong, function() {
- FlxG.state.openSubState(new SongCreationScreen(saveSong));
- }));
- #end
-
- main = new OptionsScreen(TU.translate("editor.chart.name"), TU.translate("charterSelection.desc"), list);
-
- DiscordUtil.call("onEditorTreeLoaded", ["Chart Editor"]);
- }
-
- override function createPost() {
- super.createPost();
-
- main.changeSelection(1);
- }
-
- public override function update(elapsed:Float) {
- super.update(elapsed);
-
- bg.colorTransform.redOffset = lerp(bg.colorTransform.redOffset, 0, 0.0625);
- bg.colorTransform.greenOffset = lerp(bg.colorTransform.greenOffset, 0, 0.0625);
- bg.colorTransform.blueOffset = lerp(bg.colorTransform.blueOffset, 0, 0.0625);
- bg.colorTransform.redMultiplier = lerp(bg.colorTransform.redMultiplier, 1, 0.0625);
- bg.colorTransform.greenMultiplier = lerp(bg.colorTransform.greenMultiplier, 1, 0.0625);
- bg.colorTransform.blueMultiplier = lerp(bg.colorTransform.blueMultiplier, 1, 0.0625);
- }
-
- public override function onMenuChange() {
- super.onMenuChange();
- if (optionsTree.members.length > 1) { // selected a song
- if(main != null) {
- var opt = main.members[main.curSelected];
- if(opt is EditorIconOption) {
- var opt:EditorIconOption = cast opt;
-
- // small flashbang
- var color = opt.flashColor;
- bg.colorTransform.redOffset = 0.25 * color.red;
- bg.colorTransform.greenOffset = 0.25 * color.green;
- bg.colorTransform.blueOffset = 0.25 * color.blue;
- bg.colorTransform.redMultiplier = FlxMath.lerp(1, color.redFloat, 0.25);
- bg.colorTransform.greenMultiplier = FlxMath.lerp(1, color.greenFloat, 0.25);
- bg.colorTransform.blueMultiplier = FlxMath.lerp(1, color.blueFloat, 0.25);
- }
- }
- }
+ for (i => s in freeplayList.songs) add(makeSongOption(s));
}
#if sys
public function saveSong(creation:SongCreationData, ?callback:String -> SongCreationData -> Void) {
- var songAlreadyExists:Bool = [for (s in freeplayList.songs) s.name.toLowerCase()].contains(creation.meta.name.toLowerCase());
-
+ var songAlreadyExists:Bool = songList.contains(creation.meta.name.toLowerCase());
if (songAlreadyExists) {
- openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.song-exists-title"), TU.translate("chartCreation.warnings.song-exists-body"), [
- {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}},
+ parent.openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.song-exists-title"), TU.translate("chartCreation.warnings.song-exists-body"), [
+ {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}},
]));
return;
}
- // Paths
- var songsDir:String = '${Paths.getAssetsRoot()}/songs/';
- var songFolder:String = '$songsDir${creation.meta.name}';
+ var songFolder:String = '${Paths.getAssetsRoot()}/songs/${creation.meta.name}';
#if sys
// Make Directories
- sys.FileSystem.createDirectory(songFolder);
+ CoolUtil.addMissingFolders(songFolder);
sys.FileSystem.createDirectory('$songFolder/song');
sys.FileSystem.createDirectory('$songFolder/charts');
@@ -125,52 +90,30 @@ class CharterSelection extends EditorTreeMenu {
if (creation.oppVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Opponent.${Flags.SOUND_EXT}', creation.oppVocals);
#end
- if (callback != null)
- callback(songFolder, creation);
-
- var option = new EditorIconOption(creation.meta.name, TU.translate("charterSelection.acceptSong"), creation.meta.icon, function() {
- curSong = creation.meta;
- var list:Array = [
- for(d in creation.meta.difficulties)
- if (d != "") new TextOption(d, TU.translate("charterSelection.acceptDifficulty"), function() {
- FlxG.switchState(new Charter(creation.meta.name, d));
- })
- ];
- var newDiff = TU.translate("charterSelection.newDifficulty");
- list.push(new NewOption(newDiff, newDiff, function() {
- FlxG.state.openSubState(new ChartCreationScreen(saveChart));
- }));
- optionsTree.insert(1, new OptionsScreen(creation.meta.name, TU.translate("charterSelection.selectDifficulty"), list));
- }, creation.meta.color.getDefault(0xFFFFFFFF));
+ if (callback != null) callback(songFolder, creation);
// Add to List
freeplayList.songs.insert(0, creation.meta);
- main.insert(1, option);
+ insert(1, makeSongOption(creation.meta));
}
public function saveChart(name:String, data:ChartData) {
var difficultyAlreadyExists:Bool = curSong.difficulties.contains(name);
-
if (difficultyAlreadyExists) {
- openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.chart-exists-title"), TU.translate("chartCreation.warnings.chart-exists-body"), [
- {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}},
+ parent.openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.chart-exists-title"), TU.translate("chartCreation.warnings.chart-exists-body"), [
+ {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}},
]));
return;
}
- // Paths
var songFolder:String = '${Paths.getAssetsRoot()}/songs/${curSong.name}';
// Save Files
CoolUtil.safeSaveFile('$songFolder/charts/${name}.json', Json.stringify(data, Flags.JSON_PRETTY_PRINT));
// Add to List
- // duplicated code, todo: fix this
curSong.difficulties.push(name);
- var option = new TextOption(name, TU.translate("charterSelection.acceptDifficulty"), function() {
- FlxG.switchState(new Charter(curSong.name, name));
- });
- optionsTree.members[optionsTree.members.length-1].insert(optionsTree.members[optionsTree.members.length-1].length-1, option);
+ parent.tree.last().insert(parent.tree.last().length - 1, makeChartOption(name, curSong.name));
// Add to Meta
var meta = Json.parse(sys.io.File.getContent('$songFolder/meta.json'));
diff --git a/source/funkin/editors/stage/StageSelection.hx b/source/funkin/editors/stage/StageSelection.hx
index b78aebc3..418e0190 100644
--- a/source/funkin/editors/stage/StageSelection.hx
+++ b/source/funkin/editors/stage/StageSelection.hx
@@ -1,46 +1,47 @@
package funkin.editors.stage;
import funkin.editors.stage.StageCreationScreen.StageCreationData;
+import funkin.editors.EditorTreeMenu;
import funkin.game.Stage;
-import funkin.options.OptionsScreen;
import funkin.options.type.NewOption;
import funkin.options.type.OptionType;
import funkin.options.type.TextOption;
-class StageSelection extends EditorTreeMenu
-{
+using StringTools;
+
+class StageSelection extends EditorTreeMenu {
+ override function create() {
+ super.create();
+ DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]);
+ addMenu(new StageSelectionScreen());
+ }
+}
+
+class StageSelectionScreen extends EditorTreeMenuScreen {
public var stages:Array = [];
- public override function create()
- {
- bgType = "charter";
- super.create();
+ public function makeStageOption(stage:String):TextOption {
+ return new TextOption(stage, getID('acceptStage'), () -> {
+ FlxG.switchState(new StageEditor(stage));
+ });
+ }
+
+ public function new() {
+ super('editor.stage.name', 'stageSelection.desc', 'stageSelection.', 'newStage', 'acceptNewStage', () -> {
+ parent.openSubState(new StageCreationScreen(saveStage));
+ });
var modsList:Array = Stage.getList(true, true);
-
- var list:Array = [
- for (stage in (modsList.length == 0 ? Stage.getList(false, true) : modsList)) {
- stages.push(stage);
- new TextOption(stage, TU.translate("stageSelection.acceptStage"),
- function() {
- FlxG.switchState(new StageEditor(stage));
- });
- }
- ];
-
- list.insert(0, new NewOption(TU.translate("stageSelection.newStage"), TU.translate("stageSelection.acceptNewStage"), function() {
- FlxG.state.openSubState(new StageCreationScreen(saveStage));
- }));
-
- main = new OptionsScreen(TU.translate("editor.stage.name"), TU.translate("stageSelection.desc"), list);
-
- DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]);
+ for (stage in (modsList.length == 0 ? Stage.getList(false, true) : modsList)) {
+ stages.push(stage.toLowerCase());
+ add(makeStageOption(stage));
+ }
}
public function saveStage(creation:StageCreationData) {
- if ([for (s in stages) s.toLowerCase()].contains(creation.name.toLowerCase())) {
- openSubState(new UIWarningSubstate("Creating Stage: Error!", "The stage you are trying to create alreadly exists, if you would like to override it delete the stage first!", [
- {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
+ if (stages.contains(creation.name.toLowerCase())) {
+ parent.openSubState(new UIWarningSubstate(TU.translate("stageCreationScreen.warnings.stage-exists-title"), TU.translate("stageCreationScreen.warnings.stage-exists-body"), [
+ {label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: (t) -> {}},
]));
return;
}
@@ -51,15 +52,7 @@ class StageSelection extends EditorTreeMenu
#end
// Add to List
- var option = new TextOption(creation.name, TU.translate("stageSelection.acceptStage"), function() {
- FlxG.switchState(new StageEditor(creation.name));
- });
- optionsTree.members[optionsTree.members.length-1].insert(1, option);
- }
-
- override function createPost() {
- super.createPost();
-
- main.changeSelection(1);
+ stages.push(creation.name.toLowerCase());
+ parent.tree.last().insert(parent.tree.last().length - 1, makeStageOption(creation.name));
}
}
\ No newline at end of file
diff --git a/source/funkin/menus/PauseSubState.hx b/source/funkin/menus/PauseSubState.hx
index f652b36c..2f054d9f 100644
--- a/source/funkin/menus/PauseSubState.hx
+++ b/source/funkin/menus/PauseSubState.hx
@@ -14,7 +14,6 @@ import funkin.backend.utils.FunkinParentDisabler;
import funkin.editors.charter.Charter;
import funkin.menus.StoryMenuState;
import funkin.options.OptionsMenu;
-import funkin.options.TreeMenu;
import funkin.options.keybinds.KeybindsOptions;
class PauseSubState extends MusicBeatSubstate
@@ -166,8 +165,7 @@ class PauseSubState extends MusicBeatSubstate
persistentDraw = false;
openSubState(new KeybindsOptions());
case "Change Options":
- TreeMenu.lastState = PlayState;
- FlxG.switchState(new OptionsMenu());
+ FlxG.switchState(new OptionsMenu((_) -> FlxG.switchState(new PlayState())));
case "Exit to charter":
FlxG.switchState(new Charter(PlayState.SONG.meta.name, PlayState.difficulty, false));
case "Exit to menu":
diff --git a/source/funkin/menus/credits/CreditsCodename.hx b/source/funkin/menus/credits/CreditsCodename.hx
index 70e172dc..7051e2f6 100644
--- a/source/funkin/menus/credits/CreditsCodename.hx
+++ b/source/funkin/menus/credits/CreditsCodename.hx
@@ -9,21 +9,20 @@ import funkin.options.type.GithubIconOption;
using StringTools;
-class CreditsCodename extends funkin.options.OptionsScreen {
+class CreditsCodename extends funkin.options.TreeMenuScreen {
public var error:Bool = false;
public var totalContributions:Int = 0;
public var contribFormats:Array = [];
- public override function new()
- {
- super("Codename Engine", TU.translate("credits.allContributors"));
+ public function new() {
+ super("Codename Engine", "credits.allContributors");
tryUpdating(true);
}
// blame the secondary threads if the code has to look this bad - Nex
private var _canReset:Bool = true;
private var _downloadingSteps:Int = 0;
- public override function update(elapsed:Float) {
+ override function update(elapsed:Float) {
super.update(elapsed);
if (_downloadingSteps == 2) {
@@ -35,37 +34,37 @@ class CreditsCodename extends funkin.options.OptionsScreen {
_canReset = true;
updateMenuDesc();
}
- else if(_canReset && PlayerSettings.solo.controls.RESET) tryUpdating();
+ else if (_canReset && PlayerSettings.solo.controls.RESET) tryUpdating();
}
public function tryUpdating(forceDisplaying:Bool = false) {
updateMenuDesc(TU.translate("credits.downloadingList"));
_canReset = false;
- Main.execAsync(function() {
- if(checkUpdate() || forceDisplaying) _downloadingSteps = 2;
+ Main.execAsync(() -> {
+ if (checkUpdate() || forceDisplaying) _downloadingSteps = 2;
else _downloadingSteps = 1;
});
}
public override function updateMenuDesc(?txt:String) {
- if(!_canReset) return;
+ if (!_canReset) return;
super.updateMenuDesc(txt);
updateMarkup();
}
public function updateMarkup() {
- if(parent == null || parent.treeParent == null) return;
- var text:String = parent.treeParent.pathDesc.text;
- parent.treeParent.pathDesc.text = "";
- parent.treeParent.pathDesc.applyMarkup(text, contribFormats = [
+ if (parent == null) return;
+ var text:String = parent.descLabel.text;
+ parent.descLabel.text = "";
+ parent.descLabel.applyMarkup(text, contribFormats = [
new FlxTextFormatMarkerPair(new FlxTextFormat(Flags.MAIN_DEVS_COLOR), '*'),
new FlxTextFormatMarkerPair(new FlxTextFormat(FlxColor.interpolate(Flags.MIN_CONTRIBUTIONS_COLOR, Flags.MAIN_DEVS_COLOR, Options.contributors[curSelected].contributions / totalContributions)), '~')
]);
}
override function close() {
+ for (frmt in contribFormats) parent.descLabel.removeFormat(frmt.format);
super.close();
- for (frmt in contribFormats) parent.treeParent.pathDesc.removeFormat(frmt.format);
}
public function checkUpdate():Bool {
diff --git a/source/funkin/menus/credits/CreditsMain.hx b/source/funkin/menus/credits/CreditsMain.hx
index 76485cbf..79182bcc 100644
--- a/source/funkin/menus/credits/CreditsMain.hx
+++ b/source/funkin/menus/credits/CreditsMain.hx
@@ -3,8 +3,8 @@ package funkin.menus.credits;
import flixel.util.FlxColor;
import funkin.backend.assets.AssetSource;
import funkin.backend.system.github.GitHubContributor.CreditsGitHubContributor;
-import funkin.options.OptionsScreen;
import funkin.options.TreeMenu;
+import funkin.options.TreeMenuScreen;
import funkin.options.type.*;
import haxe.xml.Access;
@@ -12,15 +12,18 @@ class CreditsMain extends TreeMenu {
var bg:FlxSprite;
var items:Array = [];
- public override function create() {
- 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();
+ override function create() {
+ super.create();
+
+ DiscordUtil.call("onMenuLoaded", ["Credits Menu"]);
+
+ add(bg = new FlxSprite().loadAnimatedGraphic(Paths.image('menus/menuBGBlue')));
bg.antialiasing = true;
- add(bg);
+ bg.scrollFactor.set();
+ updateBG();
+
+ var first = new TreeMenuScreen('credits.name', 'credits.madePossible');
+ addMenu(first);
for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) {
var xmlPath = Paths.xml('config/credits/LIB_$i');
@@ -29,28 +32,24 @@ class CreditsMain extends TreeMenu {
var access:Access = null;
try access = new Access(Xml.parse(Paths.assetsTree.getSpecificAsset(xmlPath, "TEXT")))
catch(e) Logs.trace('[CreditsMain] Error while parsing credits.xml: ${Std.string(e)}', ERROR);
- if (access != null) for (c in parseCreditsFromXML(access)) items.push(c);
+ if (access != null) for (c in parseCreditsFromXML(access)) first.add(c);
}
}
- items.push(new TextOption("Codename Engine >", TU.translate("credits.selectCodename"), function() {
- optionsTree.add(Type.createInstance(CreditsCodename, []));
- }));
- items.push(new TextOption("Friday Night Funkin'", TU.translate("credits.selectBase"), function() {
- CoolUtil.openURL(Flags.URL_FNF_ITCH);
- }));
-
- main = new OptionsScreen(TU.translate("credits.name"), TU.translate("credits.madePossible"), items);
- super.create();
-
- DiscordUtil.call("onMenuLoaded", ["Credits Menu"]);
+ first.add(new TextOption('Codename Engine', 'credits.selectCodename', ' >', () -> addMenu(new CreditsCodename())));
+ first.add(new TextOption('Friday Night Funkin\'', 'credits.selectBase', ' >', () -> CoolUtil.openURL(Flags.URL_FNF_ITCH)));
}
- /**
- * XML STUFF
- */
- public function parseCreditsFromXML(xml:Access, source:AssetSource = BOTH):Array {
- var credsMenus:Array = [];
+ 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) * 1.15;
+ bg.screenCenter();
+ }
+
+ // XML STUFF
+ public function parseCreditsFromXML(xml:Access, source:AssetSource = BOTH):Array {
+ var credsMenus:Array = [];
for(node in xml.elements) {
var desc = node.getAtt("desc").getDefault("No Description");
@@ -94,13 +93,11 @@ class CreditsMain extends TreeMenu {
credsMenus.push(opt);
case "menu":
- credsMenus.push(new TextOption(name + " >", desc, function() {
- optionsTree.add(new OptionsScreen(name, desc, parseCreditsFromXML(node, source)));
- }));
+ credsMenus.push(new TextOption(name, desc, ' >', () -> addMenu(new TreeMenuScreen(name, desc, parseCreditsFromXML(node, source)))));
}
}
}
return credsMenus;
}
-}
+}
\ No newline at end of file
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/menus/ui/Slider.hx b/source/funkin/menus/ui/Slider.hx
new file mode 100644
index 00000000..c55d9724
--- /dev/null
+++ b/source/funkin/menus/ui/Slider.hx
@@ -0,0 +1,300 @@
+package funkin.menus.ui;
+
+import flixel.animation.FlxAnimation;
+import flixel.graphics.frames.FlxFramesCollection;
+import flixel.graphics.frames.FlxFrame;
+import flixel.math.FlxAngle;
+import flixel.math.FlxPoint;
+import flixel.math.FlxRect;
+import flixel.util.FlxDestroyUtil;
+
+class Slider extends FlxSprite {
+ public var playSound:Bool = true;
+ public var selected:Bool = false;
+ public var value:Float;
+ public var segments(default, set):Int;
+
+ public var barWidth(default, set):Float;
+ public var barHeight(default, null):Float;
+ public var showSlider:Bool = true;
+
+ public var barFramerate:Float = 24.0;
+ var __animTime:Float = 0.0;
+ var __frameTime:Int;
+ var __curSegments:Int;
+ var __barFrame:FlxFrame;
+ var __barClipRect:FlxRect;
+ var __valueWidth:Float;
+ var __cornerWidth:Float;
+ var __segmentWidth:Float;
+ var __barWidth:Float;
+ var __flipX:Bool;
+ var __flipY:Bool;
+
+ public function new(?x:Float, ?y:Float, value:Float = 0.5, barWidth:Float = 500, segments:Int = 5) {
+ super(x, y);
+ this.value = value;
+ this.barWidth = barWidth;
+ this.segments = segments;
+
+ frames = Paths.getFrames('menus/options/slider');
+ antialiasing = true;
+ }
+
+ override function initVars() {
+ super.initVars();
+ __barClipRect = FlxRect.get();
+ }
+
+ override function destroy() {
+ super.destroy();
+ __barFrame = FlxDestroyUtil.destroy(__barFrame);
+ __barClipRect = FlxDestroyUtil.put(__barClipRect);
+ }
+
+ function playAnimOrDefault(a:String, d:String) {
+ if (animation.exists(a)) animation.play(a);
+ else animation.play(d);
+ }
+
+ override function update(elapsed:Float) {
+ super.update(elapsed);
+ __frameTime = Math.floor((__animTime += elapsed) * barFramerate);
+
+ if (animation.curAnim == null) {
+ animation.play(selected ? 'selected' : 'unselected');
+ __curSegments = CoolUtil.minInt(segments, Math.floor(value * (segments + 1)));
+ }
+ else {
+ switch (animation.curAnim.name) {
+ case 'selected': if (!selected) playAnimOrDefault('deselect', 'unselected');
+ case 'unselected': if (selected) playAnimOrDefault('selecting', 'selected');
+ case 'selecting' | 'segment': if (animation.finished) animation.play('selected');
+ case 'deselect': if (animation.finished) animation.play('unselected');
+ }
+
+ if (__curSegments != (__curSegments = CoolUtil.minInt(segments, Math.floor(value * (segments + 1)))) && playSound) {
+ FlxG.sound.play(Paths.sound('menu/volume')).pitch = 0.75 + __curSegments * 0.5 / (segments + 1);
+ if (animation.curAnim.name == 'selected' && animation.exists('segment')) animation.play('segment');
+ }
+ }
+ }
+
+ override function isSimpleRender(?camera:FlxCamera):Bool return false;
+ override function drawComplex(camera:FlxCamera) {
+ __flipX = checkFlipX();
+ __flipY = checkFlipY();
+ __valueWidth = barWidth * value;
+
+ var width = barWidth / (segments + 1), x = drawCorner(camera, 0, false);
+ var end = barWidth - __cornerWidth, nextSegment = width - (segments == 0 ? 0 : __segmentWidth * 0.5);
+ while (x < end) {
+ if (x == (x = drawBar(camera, x, Math.min(nextSegment - x, end - x)))) break;
+ if (x < end && x < nextSegment + __segmentWidth * 0.5) {
+ x = drawSegment(camera, x, Math.min(__segmentWidth, nextSegment + __segmentWidth - x), CoolUtil.bound(x - end, 0, __segmentWidth));
+ }
+ nextSegment += width;
+ }
+ drawCorner(camera, x, true);
+
+ if (showSlider) {
+ _frame.prepareMatrix(_matrix, ANGLE_0, __flipX != camera.flipX, __flipY != camera.flipY);
+ prepareDrawMatrix(camera, -_frame.sourceSize.x * 0.5 + (__flipX ? barWidth - __valueWidth : __valueWidth), -(_frame.sourceSize.y - barHeight) * 0.5);
+ forceDrawFrame(camera, _frame);
+ }
+ }
+
+ inline function prepareDrawMatrix(camera:FlxCamera, x:Float, y:Float) {
+ _matrix.translate(-origin.x, -origin.y);
+
+ if (frameOffsetAngle != null && frameOffsetAngle != angle) {
+ var angleOff = (frameOffsetAngle - angle) * FlxAngle.TO_RAD;
+ var cos = Math.cos(angleOff), sin = Math.sin(angleOff);
+
+ _matrix.rotateWithTrig(cos, -sin);
+ _matrix.translate(-frameOffset.x + x, -frameOffset.y + y);
+ _matrix.rotateWithTrig(cos, sin);
+ }
+ else
+ _matrix.translate(-frameOffset.x + x, -frameOffset.y + y);
+
+ _matrix.scale(scale.x, scale.y);
+
+ if (bakedRotationAngle <= 0) {
+ updateTrig();
+ if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle);
+ }
+
+ getScreenPosition(_point, camera).subtractPoint(offset);
+ _point.add(origin.x, origin.y);
+ _matrix.translate(_point.x, _point.y);
+
+ if (isPixelPerfectRender(camera)) {
+ _matrix.tx = Math.floor(_matrix.tx);
+ _matrix.ty = Math.floor(_matrix.ty);
+ }
+
+ doAdditionalMatrixStuff(_matrix, camera);
+ }
+
+ function forceDrawFrame(camera:FlxCamera, frame:FlxFrame) {
+ if (layer != null)
+ layer.drawPixels(this, camera, frame, frame.parent.bitmap, _matrix, colorTransform, blend, antialiasing, shaderEnabled ? shader : null);
+ else
+ camera.drawPixels(frame, frame.parent.bitmap, _matrix, colorTransform, blend, antialiasing, shaderEnabled ? shader : null);
+ }
+
+ function drawCorner(camera:FlxCamera, x:Float, flip:Bool):Float {
+ var anim, frame;
+
+ if (__valueWidth > x) {
+ anim = getBarAnim(1, true);
+ frame = frames.frames[anim.frames[__frameTime % anim.numFrames]];
+
+ if (flip) __barClipRect.set(__cornerWidth - __valueWidth + x, 0, __valueWidth - x, frame.sourceSize.y);
+ else __barClipRect.set(0, 0, __valueWidth - x, frame.sourceSize.y);
+ __barFrame = frame.clipTo(__barClipRect, __barFrame);
+ __barFrame.prepareMatrix(_matrix, ANGLE_0, flip != __flipX != camera.flipX, __flipY != camera.flipY);
+ prepareDrawMatrix(camera, __flipX ? barWidth - x - frame.sourceSize.x : x, 0);
+ forceDrawFrame(camera, __barFrame);
+ }
+
+ if (__valueWidth - x < __cornerWidth) {
+ anim = getBarAnim(1, false);
+ frame = frames.frames[anim.frames[__frameTime % anim.numFrames]];
+
+ if (flip) __barClipRect.set(0, 0, __cornerWidth - __valueWidth + x, frame.sourceSize.y);
+ else __barClipRect.set(__valueWidth - x, 0, __cornerWidth - __valueWidth + x, frame.sourceSize.y);
+ __barFrame = frame.clipTo(__barClipRect, __barFrame);
+ __barFrame.prepareMatrix(_matrix, ANGLE_0, flip != __flipX != camera.flipX, __flipY != camera.flipY);
+ prepareDrawMatrix(camera, __flipX ? barWidth - x - frame.sourceSize.x : x, 0);
+ forceDrawFrame(camera, __barFrame);
+ }
+
+ return x + __cornerWidth;
+ }
+
+ function drawBar(camera:FlxCamera, x:Float, width:Float):Float {
+ if (width <= 0) return x;
+ var anim, frame, s;
+
+ if (__valueWidth > x) {
+ anim = getBarAnim(0, true);
+ frame = frames.frames[anim.frames[__frameTime % anim.numFrames]];
+ s = width / frame.sourceSize.x;
+
+ __barClipRect.set(0, 0, (__valueWidth - x) / s, frame.sourceSize.y);
+ __barFrame = frame.clipTo(__barClipRect, __barFrame);
+ __barFrame.prepareMatrix(_matrix, ANGLE_0, __flipX != camera.flipX, __flipY != camera.flipY);
+ _matrix.scale(s, 1);
+ prepareDrawMatrix(camera, __flipX ? barWidth - x - width : x, 0);
+ forceDrawFrame(camera, __barFrame);
+ }
+
+ if (__valueWidth - x < width) {
+ anim = getBarAnim(0, false);
+ frame = frames.frames[anim.frames[__frameTime % anim.numFrames]];
+ s = width / frame.sourceSize.x;
+
+ __barClipRect.set((__valueWidth - x) / s, 0, (width - __valueWidth + x) / s, frame.sourceSize.y);
+ __barFrame = frame.clipTo(__barClipRect, __barFrame);
+ __barFrame.prepareMatrix(_matrix, ANGLE_0, __flipX != camera.flipX, __flipY != camera.flipY);
+ _matrix.scale(s, 1);
+ prepareDrawMatrix(camera, __flipX ? barWidth - x - width : x, 0);
+ forceDrawFrame(camera, __barFrame);
+ }
+
+ return x + width;
+ }
+
+ function drawSegment(camera:FlxCamera, x:Float, width:Float, offset:Float):Float {
+ if (width <= 0) return x;
+ var anim, frame;
+
+ if (__valueWidth > x) {
+ anim = getBarAnim(2, true);
+ frame = frames.frames[anim.frames[__frameTime % anim.numFrames]];
+
+ __barClipRect.set(offset, 0, __valueWidth - x, frame.sourceSize.y);
+ __barFrame = frame.clipTo(__barClipRect, __barFrame);
+ __barFrame.prepareMatrix(_matrix, ANGLE_0, __flipX != camera.flipX, __flipY != camera.flipY);
+ prepareDrawMatrix(camera, __flipX ? barWidth - x - width : x, 0);
+ forceDrawFrame(camera, __barFrame);
+ }
+
+ if (__valueWidth - x < __segmentWidth) {
+ anim = getBarAnim(2, false);
+ frame = frames.frames[anim.frames[__frameTime % anim.numFrames]];
+
+ __barClipRect.set(__valueWidth - x + offset, 0, __segmentWidth - offset - __valueWidth + x, frame.sourceSize.y);
+ __barFrame = frame.clipTo(__barClipRect, __barFrame);
+ __barFrame.prepareMatrix(_matrix, ANGLE_0, __flipX != camera.flipX, __flipY != camera.flipY);
+ prepareDrawMatrix(camera, __flipX ? barWidth - x - width : x, 0);
+ forceDrawFrame(camera, __barFrame);
+ }
+
+ return x + width;
+ }
+
+ function getBarAnim(type:Int, filled:Bool):FlxAnimation {
+ var name = (type == 1 ? 'corner ' : (type == 2 ? 'segment ' : 'bar ')) + (filled ? 'filled0' : 'empty0');
+ if (animation.exists(name)) return animation.getByName(name);
+
+ animation.addByPrefix(name, name, barFramerate, true);
+ return animation.getByName(name);
+ }
+
+ inline function sliderResetFrameSize() {
+ frameWidth = Std.int(barWidth);
+ frameHeight = Std.int(barHeight);
+ _halfSize.set(0.5 * frameWidth, 0.5 * frameHeight);
+ resetSize();
+ }
+
+ override function resetHelpers() {
+ animation.addByPrefix('unselected', 'slider unselected0', 24, true);
+ animation.addByPrefix('selected', 'slider selected0', 24, true);
+ animation.addByPrefix('deselect', 'slider deselect0', 24, false);
+ animation.addByPrefix('selecting', 'slider selecting0', 24, false);
+ animation.addByPrefix('segment', 'slider segment0', 24, false);
+
+ var anim, width;
+ inline function checkSize(type) {
+ barHeight = Math.max(barHeight, frames.frames[anim.frames[0]].sourceSize.y);
+ width = frames.frames[anim.frames[0]].sourceSize.x;
+ switch (type) {
+ case 0: __barWidth = Math.max(__barWidth, width);
+ case 1: __cornerWidth = Math.max(__cornerWidth, width);
+ case 2: __segmentWidth = Math.max(__segmentWidth, width);
+ }
+ }
+
+ barHeight = 0;
+ for (type in 0...3) {
+ if ((anim = getBarAnim(type, false)) != null) checkSize(type);
+ if ((anim = getBarAnim(type, true)) != null) checkSize(type);
+ }
+
+ sliderResetFrameSize();
+ resetSizeFromFrame();
+ centerOrigin();
+
+ if (FlxG.renderBlit) {
+ dirty = true;
+ updateFramePixels();
+ }
+ }
+
+ override function set_frame(v:FlxFrame):FlxFrame {
+ super.set_frame(v);
+ if (v != null) sliderResetFrameSize();
+ return v;
+ }
+
+ function set_barWidth(v:Float):Float {
+ origin.x = _halfSize.x = 0.5 * (frameWidth = Std.int(barWidth = v));
+ return v;
+ }
+
+ function set_segments(v:Int):Int return segments = CoolUtil.maxInt(v, 0);
+}
\ No newline at end of file
diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx
index 0e31a51a..0b0bda8d 100644
--- a/source/funkin/options/Options.hx
+++ b/source/funkin/options/Options.hx
@@ -29,6 +29,7 @@ class Options
public static var autoPause:Bool = true;
public static var antialiasing:Bool = true;
public static var volume:Float = 1;
+ public static var volumeMusic:Float = 1;
public static var volumeSFX:Float = 1;
public static var week6PixelPerfect:Bool = true;
public static var gameplayShaders:Bool = true;
@@ -44,6 +45,8 @@ class Options
public static var language = "en"; // default to english, Flags.DEFAULT_LANGUAGE should not modify this
public static var streamedMusic:Bool = true;
public static var streamedVocals:Bool = true;
+ public static var quality:Int = 1;
+ public static var allowConfigWarning:Bool = true;
#if MODCHARTING_FEATURES
public static var modchartingHoldSubdivisions:Int = 4;
#end
@@ -211,9 +214,23 @@ class Options
public static function applySettings() {
applyKeybinds();
- FlxG.game.stage.quality = (FlxG.enableAntialiasing = antialiasing) ? LOW : BEST;
+
+ switch (quality) {
+ case 0:
+ antialiasing = false;
+ lowMemoryMode = true;
+ gameplayShaders = false;
+ case 1:
+ antialiasing = true;
+ lowMemoryMode = false;
+ gameplayShaders = true;
+ }
+
+ FlxG.sound.defaultMusicGroup.volume = volumeMusic;
+ FlxG.game.stage.quality = (FlxG.enableAntialiasing = antialiasing) ? BEST : LOW;
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 cba1c9af..6359a21f 100644
--- a/source/funkin/options/OptionsMenu.hx
+++ b/source/funkin/options/OptionsMenu.hx
@@ -1,103 +1,94 @@
package funkin.options;
-import funkin.backend.system.framerate.Framerate;
-import funkin.options.TreeMenu;
+import haxe.xml.Access;
+import flixel.util.typeLimit.OneOfThree;
+import funkin.editors.ui.UIState;
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) -> TreeMenuScreen>;
+ 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
- },
- {
- name: "Debug Options",
- desc: "debugOptions",
- suffix: " >",
- state: DebugOptions
}
];
var bg:FlxSprite;
+ var debugOption:TextOption;
- var addedDebugOptions:Bool = !Options.devMode;
- var debugModeButton:OptionType;
- var debugModeIndex:Int = mainOptions.length - 1;
-
- 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 FlxSprite().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]));
- }
+ addMenu(new TreeMenuScreen('optionsMenu.header.title', 'optionsMenu.header.desc', [for (o in mainOptions) new TextOption(o.name, o.desc, o.suffix != null ? o.suffix : " >", () -> {
+ 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 TreeMenuScreen)
+ addMenu(o.state);
+ else if (Reflect.isFunction(o.state)) {
+ var state:(name:String, desc:String) -> TreeMenuScreen = o.state;
+ addMenu(state(o.name, o.desc));
+ }
+ else { // o.state is Class
+ addMenu(Type.createInstance(o.state, [o.name, o.desc]));
+ }
+ }
+ })]));
+
+ checkDebugOption();
for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) {
var xmlPath = Paths.xml('config/options/LIB_$i');
@@ -106,48 +97,55 @@ class OptionsMenu extends TreeMenu {
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);
+ if (access != null) for (o in parseOptionsFromXML(access)) tree.first().add(o);
}
}
-
- doDebugOptionThing();
}
- public override function onMenuClose(m:OptionsScreen) {
- doDebugOptionThing();
- }
-
- public function reloadStrings() {
- for(o in main.members) {
- o.reloadStrings();
+ function checkDebugOption() {
+ var screen = tree.first();
+ if (Options.devMode) {
+ if (debugOption == null) {
+ screen.insert(CoolUtil.minInt(screen.length, mainOptions.length),
+ debugOption = new TextOption('optionsTree.debug-name', 'optionsTree.debug-desc', ' >', () -> addMenu(new DebugOptions()))
+ );
+ }
+ }
+ else if (debugOption != null) {
+ screen.remove(debugOption, true);
+ debugOption = flixel.util.FlxDestroyUtil.destroy(debugOption);
+ if (screen.curSelected >= screen.length) screen.changeSelection(0, true);
}
- optionsTree.reloadStrings();
- reloadLabels();
}
- public override function exit() {
+ 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) * 1.15;
+ bg.screenCenter();
+ }
+
+ override function onResize(width:Int, height:Int) {
+ super.onResize(width, height);
+ if (!UIState.resolutionAware) return;
+
+ updateBG();
+ }
+
+ override function menuChanged() {
+ super.menuChanged();
+ checkDebugOption();
+ }
+
+ override function exit() {
Options.save();
Options.applySettings();
super.exit();
}
- function doDebugOptionThing() {
- if ((Options.devMode && !addedDebugOptions) || (!Options.devMode && addedDebugOptions)) {
- trace((addedDebugOptions ? "remove" : "add") + " option");
- addedDebugOptions = !addedDebugOptions;
- if (debugModeButton == null) debugModeButton = main.members[debugModeIndex];
- if (addedDebugOptions)
- main.add(debugModeButton);
- else
- main.remove(debugModeButton);
- }
- }
-
- /**
- * XML STUFF
- */
- public function parseOptionsFromXML(xml:Access):Array {
- var options:Array = [];
+ // XML STUFF
+ public function parseOptionsFromXML(xml:Access):Array {
+ var options:Array = [];
for(node in xml.elements) {
if (!node.has.name) {
@@ -163,7 +161,7 @@ class OptionsMenu extends TreeMenu {
Logs.warn("A checkbox option requires an \"id\" for option saving.");
continue;
}
- options.push(new Checkbox(name, desc, node.att.id, FlxG.save.data));
+ options.push(new Checkbox(name, desc, node.att.id, null, FlxG.save.data));
case "number":
if (!node.has.id) {
@@ -189,9 +187,7 @@ class OptionsMenu extends TreeMenu {
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)));
- }));
+ options.push(new TextOption(name, desc, ' >', () -> addMenu(new TreeMenuScreen(name, desc, parseOptionsFromXML(node)))));
}
}
diff --git a/source/funkin/options/OptionsScreen.hx b/source/funkin/options/OptionsScreen.hx
deleted file mode 100644
index bb71aec9..00000000
--- a/source/funkin/options/OptionsScreen.hx
+++ /dev/null
@@ -1,143 +0,0 @@
-package funkin.options;
-
-import funkin.options.type.OptionType;
-
-class OptionsScreen extends FlxTypedSpriteGroup {
- public static var optionHeight:Float = 120;
-
- public var parent:OptionsTree;
-
- public var curSelected:Int = 0;
- public var id:Int = 0;
-
- private var __firstFrame:Bool = true;
-
- 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;
-
- public inline function getNameID(name):String {
- return prefix + name + "-name";
- }
- public inline function getDescID(name):String {
- return prefix + name + "-desc";
- }
-
- public function getName(name:String, ?args:Array):String {
- return TU.translate(getNameID(name), args);
- }
- public function getDesc(name:String, ?args:Array):String {
- return TU.translate(getDescID(name), args);
- }
- public function translate(name:String, ?args:Array):String {
- return TU.translate(prefix + name, args);
- }
-
- public function new(name:String, desc:String, ?prefix:String, ?options:Array) {
- super();
- rawName = name;
- rawDesc = desc;
- this.prefix = prefix != null ? prefix : "";
- if (options != null) for(o in options) add(o);
- }
-
- function set_rawName(v:String) {
- rawName = v;
- this.name = TU.exists(rawName) ? TU.translate(rawName) : rawName;
- return v;
- }
-
- function set_rawDesc(v:String) {
- rawDesc = v;
- this.desc = TU.exists(rawDesc) ? TU.translate(rawDesc) : rawDesc;
- return v;
- }
-
- public function reloadStrings() {
- this.rawName = rawName;
- this.rawDesc = rawDesc;
-
- for(o in members) {
- o.reloadStrings();
- }
- }
-
- public override function update(elapsed:Float) {
- super.update(elapsed);
-
- var controls = PlayerSettings.solo.controls;
-
- changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0) - FlxG.mouse.wheel);
- x = id * FlxG.width;
- for(k=>option in members) {
- if(option == null) continue;
-
- var y:Float = ((FlxG.height - optionHeight) / 2) + ((k - curSelected) * optionHeight);
-
- option.selected = false;
- option.y = __firstFrame ? y : CoolUtil.fpsLerp(option.y, y, 0.25);
- option.x = x + (-50 + (Math.abs(Math.cos((option.y + (optionHeight / 2) - (FlxG.camera.scroll.y + (FlxG.camera.height / 2))) / (FlxG.camera.height * 1.25) * Math.PI)) * 150));
- }
- if (__firstFrame) {
- __firstFrame = false;
- return;
- }
-
- if (members.length > 0) {
- members[curSelected].selected = true;
- if (controls.ACCEPT || (FlxG.mouse.justReleased && Main.timeSinceFocus > 0.25)) {
- members[curSelected].onSelect();
- onSelect(members[curSelected]);
- }
- if (controls.LEFT_P)
- members[curSelected].onChangeSelection(-1);
- if (controls.RIGHT_P)
- members[curSelected].onChangeSelection(1);
- }
- if (controls.BACK || FlxG.mouse.justReleasedRight)
- close();
- }
-
- public function close() {
- onClose(this);
- }
-
- public function changeSelection(sel:Int, force:Bool = false) {
- if (members.length <= 0 || (sel == 0 && !force)) return;
-
- CoolUtil.playMenuSFX(SCROLL);
- curSelected = FlxMath.wrap(curSelected + sel, 0, members.length-1);
- if (members[curSelected] == null) {
- changeSelection(sel, true);
- return;
- }
- members[curSelected].selected = true;
- updateMenuDesc();
- }
-
- public function updateMenuDesc(?customTxt:String) {
- if (parent == null || parent.treeParent == null) return;
- parent.treeParent.updateDesc(customTxt != null ? customTxt : members[curSelected].desc);
- }
-
- public dynamic function onClose(o:OptionsScreen) {}
- public dynamic function onSelect(o:OptionType) {
- if(o is funkin.options.type.RadioButton) {
- var orb:funkin.options.type.RadioButton = cast o;
- for(e in members) {
- if(e is funkin.options.type.RadioButton) {
- var rb:funkin.options.type.RadioButton = cast e;
- if(rb.forId == orb.forId && rb != o) {
- rb.checked = false;
- }
- }
- }
- }
- }
-}
\ 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..33ae47c6 100644
--- a/source/funkin/options/TreeMenu.hx
+++ b/source/funkin/options/TreeMenu.hx
@@ -1,148 +1,298 @@
package funkin.options;
-import flixel.FlxState;
import flixel.tweens.FlxTween;
-import flixel.util.typeLimit.OneOfTwo;
-import flixel.util.typeLimit.OneOfThree;
+import flixel.util.FlxArrayUtil;
+import flixel.util.FlxColor;
+import flixel.util.FlxSignal;
import funkin.backend.FunkinText;
-import funkin.editors.ui.UIState;
-import funkin.menus.MainMenuState;
-import funkin.options.type.OptionType;
-import funkin.options.type.TextOption;
import funkin.backend.system.framerate.Framerate;
+import funkin.editors.ui.UIState;
+
+interface ITreeOption {
+ var desc:String;
+ var selected:Bool;
+
+ function changeSelection(change:Int):Void;
+ function select():Void;
+}
+
+interface ITreeFloatOption extends ITreeOption {
+ function changeValue(change:Float):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 treeLength(default, null):Int = 0;
+ public var previousMenus:Array = [];
+ 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;
+ var __treeCreated:Bool = false;
+
+ 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.width - 8, 32);
+ titleLabel.borderSize = 1.25;
+ titleLabel.scrollFactor.set();
+ descLabel = new FunkinText(4, 0, FlxG.width - 8, 16);
+ descLabel.scrollFactor.set();
+ }
+ override function createPost() {
super.createPost();
+
+ if ((treeLength = tree.length) != 0) {
+ updateMenuPositions(true);
+ tree.last().inputEnabled = true;
+ }
+ else
+ addMenu(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);
+
+ FlxG.camera.scroll.x = -FlxG.width;
+ menuChanged();
+
+ __treeCreated = true;
}
- 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[fromIndex - 1], menu:TreeMenuScreen;
+ while (fromIndex < treeLength) if ((menu = tree[fromIndex++]) != null) {
+ menu.x = last == null ? 0 : last.x + Math.max(FlxG.width, last.width);
+ (last = menu).parent = this;
+ }
- 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 (cameraScroll && last != null) {
+ if (menuChangeTween != null && menuChangeTween.active) menuChangeTween.cancel();
+ FlxG.camera.scroll.x = last.x;
- reloadLabels();
+ for (menu in tree) if (menu != null) menu.transitioning = false;
+ for (menu in previousMenus) if (menu != null) menu.transitioning = false;
}
}
- public function onMenuClose(m:OptionsScreen) {
- CoolUtil.playMenuSFX(CANCEL);
+ 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 reloadLabels() {
- var t = "";
- for(o in optionsTree.members)
- t += '${o.name} > ';
- pathLabel.text = t;
+ public function updateDesc(?customText:String) {
+ var last = tree.last();
- var idk:OptionsScreen = optionsTree.members.last();
- if (idk.members.length > 0) updateDesc(idk.members[idk.curSelected].desc);
+ 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 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 addMenu(menu:TreeMenuScreen):TreeMenuScreen {
+ if (menu == null) return null;
+ if (tree.indexOf(menu) != -1) return menu;
+
+ tree.push(menu);
+ if (!__treeCreated) return menu;
+
+ menu.parent = this;
+ treeLength++;
+
+ var prev = tree[treeLength - 2];
+ menu.x = prev == null ? 0 : prev.x + Math.max(FlxG.width, prev.width);
+
+ destroyPreviousMenus();
+ menuChanged();
+ menu.inputEnabled = true;
+ if (prev != null) prev.inputEnabled = false;
+
+ return menu;
}
- public function exit() {
- FlxG.switchState((lastState != null) ? Type.createInstance(lastState, []) : new MainMenuState());
- lastState = null;
+ public function insertMenu(position:Int, menu:TreeMenuScreen):TreeMenuScreen {
+ if (menu == null) return null;
+ if (tree.indexOf(menu) != -1) return menu;
+
+ if (position < 0) position = treeLength - ((-position - 1) % treeLength);
+
+ tree.insert(position, menu);
+ if (!__treeCreated) return menu;
+
+ menu.parent = this;
+
+ var lastChanged = position >= treeLength++;
+ updateMenuPositions(position, !lastChanged);
+
+ if (lastChanged) {
+ destroyPreviousMenus();
+ menuChanged();
+ menu.inputEnabled = true;
+ if (treeLength > 0) tree[treeLength - 2].inputEnabled = false;
+ }
+
+ return menu;
}
- var menuChangeTween:FlxTween;
- public override function update(elapsed:Float) {
+ public function popMenu():TreeMenuScreen
+ return removeMenuPosition(treeLength - 1);
+
+ public function removeMenu(menu:TreeMenuScreen):TreeMenuScreen
+ return if (menu == null) null; else removeMenuPosition(tree.indexOf(menu));
+
+ public function removeMenuPosition(position:Int):TreeMenuScreen {
+ if (position < 0 || position >= treeLength || treeLength == 0) return null;
+
+ tree[position] = tree[tree.length - 1];
+ var menu = tree.pop();
+ menu.parent = null;
+
+ if (!__treeCreated) return menu;
+
+ previousMenus.push(menu);
+ if (position == --treeLength) {
+ menuChanged();
+ menu.inputEnabled = false;
+ if (treeLength > 0) tree[treeLength - 1].inputEnabled = true;
+ }
+ else {
+ updateMenuPositions(position, true);
+ updateLabels();
+ }
+
+ onMenuClosed.dispatch(menu);
+ return menu;
+ }
+
+ public function menuChanged() {
+ if (treeLength == 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;
+ for (menu in previousMenus) if (menu != null) menu.transitioning = false;
+ }});
+
+ for (menu in tree) if (menu != null) menu.transitioning = true;
+ }
+
+ for (menu in previousMenus) if (menu != null) menu.transitioning = true;
+ }
+
+ 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 < treeLength) {
+ if ((menu = tree[i++]) == null || !menu.active || !menu.exists) continue;
+ if (i == treeLength || menu.persistentUpdate || menu.transitioning) menu.update(elapsed);
+ }
+
+ i = previousMenus.length;
+ while (i-- > 0) {
+ if ((menu = previousMenus[i]).transitioning) menu.update(elapsed);
+ else {
+ menu.destroy();
+ FlxArrayUtil.swapAndPop(previousMenus, i);
+ }
+ }
+
+ // 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();
+ destroyPreviousMenus();
+ }
+
+ 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.scale.x = width) - 8;
+ bgLabel.updateHitbox();
}
- 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();
+ updateLabels();
+ }
+
+ public function destroyPreviousMenus() {
+ for (menu in previousMenus) menu.destroy();
+ previousMenus.resize(0);
+ }
+
+ public function exit() {
+ if (exitCallback != null) return exitCallback(this);
+
+ FlxG.switchState(new funkin.menus.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) {
+ super();
+ this.parent = parent;
+ }
-typedef OptionTypeDef = {
- var type:Class;
- var args:Array;
-}
+ override function draw() {
+ var i = 0, menu:TreeMenuScreen;
+ while (i < parent.treeLength) {
+ if ((menu = parent.tree[i++]) == null || !menu.active || !menu.exists) continue;
+ if (i == parent.treeLength || menu.persistentUpdate || menu.transitioning) menu.draw();
+ }
+
+ i = parent.previousMenus.length;
+ while (i-- > 0) if (parent.previousMenus[i] != null) parent.previousMenus[i].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..33883fc0
--- /dev/null
+++ b/source/funkin/options/TreeMenuScreen.hx
@@ -0,0 +1,192 @@
+package funkin.options;
+
+import flixel.group.FlxSpriteGroup;
+import flixel.util.FlxSignal;
+import funkin.backend.system.Controls;
+import funkin.backend.TurboControls;
+import funkin.options.TreeMenu.ITreeOption;
+import funkin.options.TreeMenu.ITreeFloatOption;
+import funkin.options.type.OptionType;
+import funkin.options.type.Separator;
+
+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 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) {
+ rawName = v;
+ name = TU.exists(v) ? TU.translate(v) : v;
+ return v;
+ }
+
+ function set_rawDesc(v:String) {
+ rawDesc = v;
+ desc = TU.exists(v) ? TU.translate(v) : v;
+ return v;
+ }
+
+ public inline function getNameID(name):String return prefix + name + "-name";
+ public inline function getDescID(name):String return prefix + name + "-desc";
+ public inline function getID(name):String return prefix + name;
+ public function translate(name:String, ?args:Array):String return TU.translate(getID(name), args);
+
+ public var controls(get, never):Controls;
+ inline function get_controls():Controls return PlayerSettings.solo.controls;
+
+ var leftTurboControl:TurboControls = new TurboControls([Control.LEFT], 0.2, 1 / 48);
+ var rightTurboControl:TurboControls = new TurboControls([Control.RIGHT], 0.2, 1 / 48);
+ var upTurboControl:TurboControls = new TurboControls([Control.UP]);
+ var downTurboControl:TurboControls = new TurboControls([Control.DOWN]);
+ var turboBasics:Array;
+
+ var curOption:ITreeOption;
+ var curFloatOption:ITreeFloatOption;
+ var __firstFrame:Bool = true;
+
+ public function new(name:String, desc:String, ?prefix:String, ?objects:Array) {
+ super();
+ this.prefix = prefix;
+ rawName = name;
+ rawDesc = desc;
+
+ turboBasics = [leftTurboControl, rightTurboControl, upTurboControl, downTurboControl];
+
+ if (objects != null) for (object in objects) add(object);
+ }
+
+ public function reloadStrings() {
+ rawName = rawName;
+ rawDesc = rawDesc;
+
+ for (object in members) if (object != null && object is OptionType) cast(object, OptionType).reloadStrings();
+ }
+
+ override function update(elapsed:Float) {
+ super.update(elapsed);
+
+ if (__firstFrame) {
+ __firstFrame = false;
+ if (members[curSelected] is ITreeOption) {
+ (curOption = cast members[curSelected]).selected = true;
+ if (curOption is ITreeFloatOption) curFloatOption = cast curOption;
+ }
+ updateItems(true);
+ return;
+ }
+
+ if (inputEnabled) {
+ for (basic in turboBasics) basic.update(elapsed);
+
+ var change = (upTurboControl.activated ? -1 : 0) + (downTurboControl.activated ? 1 : 0) - FlxG.mouse.wheel, mouseControl = false;
+ if (FlxG.mouse.justPressed) {
+ for (i in CoolUtil.maxInt(curSelected - 3, 0)...CoolUtil.minInt(curSelected + 4, length))
+ if (i != curSelected && members[i] != null && mouseOverlaps(members[i])) {
+ change = i - curSelected;
+ mouseControl = true;
+ break;
+ }
+ }
+ changeSelection(change);
+
+ if (length > 0 && curOption != null) {
+ if (controls.ACCEPT || (!mouseControl && FlxG.mouse.justPressed && mouseOverlaps(members[curSelected]))) curOption.select();
+ if (curFloatOption != null) {
+ if (controls.LEFT) curFloatOption.changeValue(-elapsed);
+ if (controls.RIGHT) curFloatOption.changeValue(elapsed);
+ }
+ else {
+ if (leftTurboControl.activated) curOption.changeSelection(-1);
+ if (rightTurboControl.activated) curOption.changeSelection(1);
+ }
+ }
+
+ if (controls.BACK || (FlxG.mouse.justPressedRight && Main.timeSinceFocus > 0.3)) close();
+ }
+
+ updateItems();
+ }
+
+ public function updateItems(force = false) {
+ var r = force ? 1 : 0.25, initY = FlxG.height * 0.5;
+ var i = curSelected, y = initY, object:FlxSprite = null, itemHeight:Float = 0;
+
+ inline function updateItem() {
+ object.y = CoolUtil.fpsLerp(object.y, y - (itemHeight = object.height) * 0.5, r);
+ object.x = x + 100 - Math.pow(Math.abs((object.y - (FlxG.height - itemHeight) * 0.5) / itemHeight / FlxG.height * FlxG.initialHeight), 1.6) * 15;
+ }
+
+ while (i < length) if ((object = members[i++]) != null) {
+ updateItem();
+ y += itemHeight;
+ }
+
+ y = initY;
+ i = curSelected;
+ while (i-- > 0) if ((object = members[i]) != null) {
+ y -= itemHeight;
+ updateItem();
+ }
+ }
+
+ public function close() {
+ onClose.dispatch();
+
+ if (curOption != null) curOption.selected = false;
+
+ if (parent == null) return destroy();
+ else parent.removeMenu(this);
+
+ CoolUtil.playMenuSFX(CANCEL).persist = true;
+ }
+
+ public function changeSelection(change:Int, force:Bool = false) {
+ if (length == 0 || (change == 0 && !force)) return;
+
+ var prevSelect = curSelected = FlxMath.wrap(curSelected + change, 0, members.length - 1);
+ while (members[curSelected] is Separator)
+ if ((curSelected = FlxMath.wrap(curSelected + (change > 0 ? 1 : -1), 0, members.length - 1)) == prevSelect) break;
+
+ if (curOption != null) curOption.selected = false;
+ if (members[curSelected] is ITreeOption) {
+ (curOption = cast members[curSelected]).selected = true;
+ if (curOption is ITreeFloatOption) curFloatOption = cast curOption;
+ else curFloatOption = null;
+ }
+ else {
+ curOption = null;
+ curFloatOption = null;
+ }
+ updateMenuDesc();
+
+ CoolUtil.playMenuSFX(SCROLL);
+ }
+
+ public function updateMenuDesc(?customTxt:String) {
+ if (parent != null) parent.updateDesc(customTxt);
+ }
+
+ function mouseOverlaps(sprite:FlxSprite):Bool
+ return sprite.overlapsPoint(FlxG.mouse.getPosition(@:privateAccess flixel.input.FlxPointer._cachedPoint), true);
+
+ 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..0f55e496 100644
--- a/source/funkin/options/categories/AppearanceOptions.hx
+++ b/source/funkin/options/categories/AppearanceOptions.hx
@@ -1,55 +1,63 @@
package funkin.options.categories;
-class AppearanceOptions extends OptionsScreen {
- public override function new(title:String, desc:String) {
- super(title, desc, "AppearanceOptions.");
- add(new NumOption(
- getName("framerate"),
- getDesc("framerate"),
- 30, // minimum
- 240, // maximum
- 10, // change
- "framerate", // save name or smth
- __changeFPS)); // callback
- add(new Checkbox(
- getName("antialiasing"),
- getDesc("antialiasing"),
- "antialiasing"));
- add(new Checkbox(
- getName("colorHealthBar"),
- getDesc("colorHealthBar"),
- "colorHealthBar"));
- add(new Checkbox(
- getName("week6PixelPerfect"),
- getDesc("week6PixelPerfect"),
- "week6PixelPerfect"));
- add(new Checkbox(
- getName("gameplayShaders"),
- getDesc("gameplayShaders"),
- "gameplayShaders"));
- add(new Checkbox(
- getName("flashingMenu"),
- getDesc("flashingMenu"),
- "flashingMenu"));
- add(new Checkbox(
- getName("lowMemoryMode"),
- getDesc("lowMemoryMode"),
- "lowMemoryMode"));
- #if sys
- if (!Main.forceGPUOnlyBitmapsOff) {
- add(new Checkbox(
- getName("gpuOnlyBitmaps"),
- getDesc("gpuOnlyBitmaps"),
- "gpuOnlyBitmaps"));
- }
- #end
+class AppearanceOptions extends TreeMenuScreen {
+ public function new() {
+ super('optionsTree.appearance-name', 'optionsTree.appearance-desc', 'AppearanceOptions.');
+
+ add(new NumOption(getNameID('framerate'), getDescID('framerate'),
+ 30, 240, 1,
+ 'framerate', __changeFPS
+ ));
+ add(new Checkbox(getNameID('flashingMenu'), getDescID('flashingMenu'), 'flashingMenu'));
+ add(new Checkbox(getNameID('colorHealthBar'), getDescID('colorHealthBar'), 'colorHealthBar'));
+ add(new Checkbox(getNameID('week6PixelPerfect'), getDescID('week6PixelPerfect'), 'week6PixelPerfect'));
+
+ add(new Separator());
+ add(new TextOption('optionsMenu.advanced', 'optionsTree.appearance.advanced-desc', ' >', () ->
+ parent.addMenu(new AdvancedAppearanceOptions())));
}
- 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;
+ }
+}
+
+class AdvancedAppearanceOptions extends TreeMenuScreen {
+ var qualityOptions:Array = [];
+
+ public function new() {
+ super('optionsMenu.advanced', 'optionsTree.appearance.advanced-desc', 'AppearanceOptions.Advanced.');
+
+ add(new ArrayOption(getNameID('quality'), getDescID('quality'),
+ [0, 1, 2], [getID('quality-low'), getID('quality-high'), getID('quality-custom')],
+ 'quality', __changeQuality, null
+ ));
+
+ for (option in (qualityOptions = [
+ new Checkbox(getNameID('antialiasing'), getDescID('antialiasing'), 'antialiasing', __changeAntialiasing),
+ new Checkbox(getNameID('lowMemoryMode'), getDescID('lowMemoryMode'), 'lowMemoryMode'),
+ new Checkbox(getNameID('gameplayShaders'), getDescID('gameplayShaders'), 'gameplayShaders')
+ ]))
+ add(option);
+
+ add(new Checkbox(getNameID('gpuOnlyBitmaps'), getDescID('gpuOnlyBitmaps'), 'gpuOnlyBitmaps'));
+
+ updateQualityOptions();
+ }
+
+ private function updateQualityOptions() {
+ for (option in qualityOptions) option.locked = Options.quality != 2;
+ }
+
+ private function __changeQuality(value:Dynamic) {
+ var antialiasing = value == 0 ? false : (value == 1 ? true : Options.antialiasing);
+ FlxG.game.stage.quality = (FlxG.enableAntialiasing = antialiasing) ? BEST : LOW;
+ updateQualityOptions();
+ }
+
+ private function __changeAntialiasing() {
+ FlxG.game.stage.quality = (FlxG.enableAntialiasing = Options.antialiasing) ? BEST : LOW;
}
}
\ No newline at end of file
diff --git a/source/funkin/options/categories/DebugOptions.hx b/source/funkin/options/categories/DebugOptions.hx
index 16676890..b0ff8e71 100644
--- a/source/funkin/options/categories/DebugOptions.hx
+++ b/source/funkin/options/categories/DebugOptions.hx
@@ -1,62 +1,22 @@
package funkin.options.categories;
-import funkin.backend.utils.NativeAPI;
+class DebugOptions extends TreeMenuScreen {
+ public function new() {
+ super('DebugOptions.title', 'DebugOptions.desc', 'DebugOptions.');
-class DebugOptions extends OptionsScreen {
- public override function new(title:String, desc:String) {
- super(title, desc, "DebugOptions.");
-
- add(new TextOption(
- getName("showConsole"),
- getDesc("showConsole"),
- function() NativeAPI.allocConsole()));
- add(new Checkbox(
- getName("editorsResizable"),
- getDesc("editorsResizable"),
- "editorsResizable"));
- add(new Checkbox(
- getName("bypassEditorsResize"),
- getDesc("bypassEditorsResize"),
- "bypassEditorsResize"));
- add(new Checkbox(
- getName("editorSFX"),
- getDesc("editorSFX"),
- "editorSFX"));
- add(new Checkbox(
- getName("editorCharterPrettyPrint"),
- getDesc("editorCharterPrettyPrint"),
- "editorCharterPrettyPrint"));
- add(new Checkbox(
- getName("editorCharacterPrettyPrint"),
- getDesc("editorCharacterPrettyPrint"),
- "editorCharacterPrettyPrint"));
- add(new Checkbox(
- getName("editorStagePrettyPrint"),
- getDesc("editorStagePrettyPrint"),
- "editorStagePrettyPrint"));
- add(new Checkbox(
- getName("intensiveBlur"),
- getDesc("intensiveBlur"),
- "intensiveBlur"));
- add(new Checkbox(
- getName("charterAutoSaves"),
- getDesc("charterAutoSaves"),
- "charterAutoSaves"));
- add(new NumOption(
- getName("charterAutoSaveTime"),
- getDesc("charterAutoSaveTime"),
- 60, 60*10, 30,
- "charterAutoSaveTime"
- ));
- add(new NumOption(
- getName("charterAutoSaveWarningTime"),
- getDesc("charterAutoSaveWarningTime"),
- 0, 15, 1,
- "charterAutoSaveWarningTime"
- ));
- add(new Checkbox(
- getName("charterAutoSavesSeparateFolder"),
- getDesc("charterAutoSavesSeparateFolder"),
- "charterAutoSavesSeparateFolder"));
+ #if windows
+ add(new TextOption(getNameID("showConsole"), getDescID("showConsole"), () -> funkin.backend.utils.NativeAPI.allocConsole()));
+ #end
+ add(new Checkbox(getNameID("editorsResizable"), getDescID("editorsResizable"), "editorsResizable"));
+ add(new Checkbox(getNameID("bypassEditorsResize"), getDescID("bypassEditorsResize"), "bypassEditorsResize"));
+ add(new Checkbox(getNameID("editorSFX"), getDescID("editorSFX"), "editorSFX"));
+ add(new Checkbox(getNameID("editorCharterPrettyPrint"), getDescID("editorCharterPrettyPrint"), "editorCharterPrettyPrint"));
+ add(new Checkbox(getNameID("editorCharacterPrettyPrint"), getDescID("editorCharacterPrettyPrint"), "editorCharacterPrettyPrint"));
+ add(new Checkbox(getNameID("editorStagePrettyPrint"), getDescID("editorStagePrettyPrint"), "editorStagePrettyPrint"));
+ add(new Checkbox(getNameID("intensiveBlur"), getDescID("intensiveBlur"), "intensiveBlur"));
+ add(new Checkbox(getNameID("charterAutoSaves"), getDescID("charterAutoSaves"), "charterAutoSaves"));
+ add(new NumOption(getNameID("charterAutoSaveTime"), getDescID("charterAutoSaveTime"), 60, 60*10, 1, "charterAutoSaveTime"));
+ add(new NumOption(getNameID("charterAutoSaveWarningTime"), getDescID("charterAutoSaveWarningTime"), 0, 15, 1, "charterAutoSaveWarningTime"));
+ add(new Checkbox(getNameID("charterAutoSavesSeparateFolder"), getDescID("charterAutoSavesSeparateFolder"), "charterAutoSavesSeparateFolder"));
}
}
\ No newline at end of file
diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx
index 4e1f8a7e..7d66f351 100644
--- a/source/funkin/options/categories/GameplayOptions.hx
+++ b/source/funkin/options/categories/GameplayOptions.hx
@@ -3,64 +3,39 @@ package funkin.options.categories;
import flixel.util.FlxTimer;
import funkin.backend.system.Conductor;
-class GameplayOptions extends OptionsScreen {
+class GameplayOptions extends TreeMenuScreen {
var __metronome = FlxG.sound.load(Paths.sound('editors/charter/metronome'));
-
var offsetSetting:NumOption;
- public override function new(title:String, desc:String) {
- super(title, desc, "GameplayOptions.");
- add(new Checkbox(
- getName("downscroll"),
- getDesc("downscroll"),
- "downscroll"));
- add(new Checkbox(
- getName("ghostTapping"),
- getDesc("ghostTapping"),
- "ghostTapping"));
- add(new Checkbox(
- getName("naughtyness"),
- getDesc("naughtyness"),
- "naughtyness"));
- add(new Checkbox(
- getName("camZoomOnBeat"),
- getDesc("camZoomOnBeat"),
- "camZoomOnBeat"));
- add(new Checkbox(
- getName("autoPause"),
- getDesc("autoPause"),
- "autoPause"));
- add(new MeterOption(
- getName("volumeSFX"),
- getDesc("volumeSFX"),
- 0, // minimum
- 1, // maximum
- 0.1, // change
- "volumeSFX"));
- add(offsetSetting = new NumOption(
- getName("songOffset"),
- getDesc("songOffset"),
- -999, // minimum
- 999, // maximum
- 1, // change
- "songOffset", // save name or smth
- __changeOffset)); // callback
+ public function new() {
+ super('optionsTree.gameplay-name', 'optionsTree.gameplay-desc', 'GameplayOptions.');
+
+ add(new Checkbox(getNameID('downscroll'), getDescID('downscroll'), 'downscroll'));
+ add(new Checkbox(getNameID('ghostTapping'), getDescID('ghostTapping'), 'ghostTapping'));
+ add(new Checkbox(getNameID('naughtyness'), getDescID('naughtyness'), 'naughtyness'));
+ add(new Checkbox(getNameID('camZoomOnBeat'), getDescID('camZoomOnBeat'), 'camZoomOnBeat'));
+ add(new Checkbox(getNameID('autoPause'), getDescID('autoPause'), 'autoPause', __changeAutoPause));
+ add(offsetSetting = new NumOption(getNameID('songOffset'), getDescID('songOffset'), -999, 999, 1, 'songOffset', __changeOffset));
+ add(new SliderOption(getNameID('volumeMusic'), getDescID('volumeMusic'), 0, 1, 1, 5, 'volumeMusic', -1, __changeVolumeMusic));
+ add(new SliderOption(getNameID('volumeSFX'), getDescID('volumeSFX'), 0, 1, 1, 5, 'volumeSFX'));
+
+ add(new Separator());
add(new TextOption('optionsMenu.advanced', 'optionsTree.gameplay.advanced-desc', ' >', () ->
- parent.add(new AdvancedGameplayOptions('optionsMenu.advanced', 'optionsTree.gameplay.advanced-desc'))));
+ parent.addMenu(new AdvancedGameplayOptions())));
}
- private function __changeOffset(offset)
- Conductor.songOffset = offset;
+ private function __changeAutoPause() FlxG.autoPause = Options.autoPause;
+ private function __changeOffset(offset:Float) Conductor.songOffset = offset;
+ private function __changeVolumeMusic(value:Float) FlxG.sound.defaultMusicGroup.volume = value;
var __lastBeat:Int = 0;
var __lastSongBeat:Int = 0;
- override function update(elapsed) {
+ override function update(elapsed:Float) {
super.update(elapsed);
- FlxG.camera.zoom = CoolUtil.fpsLerp(FlxG.camera.zoom, 1, 0.04);
+ FlxG.camera.zoom = CoolUtil.fpsLerp(FlxG.camera.zoom, 1, 0.1);
if (offsetSetting.selected) {
- FlxG.sound.music.volume = 0.5;
if (__lastBeat != Conductor.curBeat) {
FlxG.camera.zoom += 0.03;
__lastBeat = Conductor.curBeat;
@@ -72,27 +47,27 @@ class GameplayOptions extends OptionsScreen {
__lastSongBeat = beat;
}
}
- else
- FlxG.sound.music.volume = 1;
+ }
+
+ override function changeSelection(change:Int, force:Bool = false) {
+ super.changeSelection(change, force);
+ if (offsetSetting.selected) FlxG.sound.music.volume = 0.5;
+ else FlxG.sound.music.volume = 1;
}
override function close() {
+ super.close();
+
FlxG.camera.zoom = 1;
FlxG.sound.music.volume = 1;
- super.close();
}
}
-class AdvancedGameplayOptions extends OptionsScreen {
- public override function new(title:String, desc:String) {
- super(title, desc, "GameplayOptions.Advanced.");
- add(new Checkbox(
- getName("streamedMusic"),
- getDesc("streamedMusic"),
- "streamedMusic"));
- add(new Checkbox(
- getName("streamedVocals"),
- getDesc("streamedVocals"),
- "streamedVocals"));
+class AdvancedGameplayOptions extends TreeMenuScreen {
+ public function new() {
+ super('optionsMenu.advanced', 'optionsTree.gameplay.advanced-desc', 'GameplayOptions.Advanced.');
+
+ add(new Checkbox(getNameID('streamedMusic'), getDescID('streamedMusic'), 'streamedMusic'));
+ add(new Checkbox(getNameID('streamedVocals'), getDescID('streamedVocals'), 'streamedVocals'));
}
-}
+}
\ No newline at end of file
diff --git a/source/funkin/options/categories/LanguageOptions.hx b/source/funkin/options/categories/LanguageOptions.hx
index b3e64433..878e6741 100644
--- a/source/funkin/options/categories/LanguageOptions.hx
+++ b/source/funkin/options/categories/LanguageOptions.hx
@@ -3,17 +3,13 @@ package funkin.options.categories;
class LanguageRadio extends RadioButton {
public var langID:String;
- public function new(name:String, langID:String) {
+ public function new(screen:TreeMenuScreen, name:String, langID:String) {
+ super(screen, name, "LanguageOptions.language-desc", null, langID, null, "languageSelector");
this.langID = langID;
- super(name, "LanguageOptions.language-desc", null, langID, null, "languageSelector");
checked = langID == TU.curLanguage;
}
- override function reloadStrings() {
- super.reloadStrings();
- }
-
override function set_rawDesc(v:String) {
rawDesc = v;
var config = TU.getConfig(langID);
@@ -23,28 +19,25 @@ class LanguageRadio extends RadioButton {
return v;
}
- override dynamic function onSet(value:Dynamic) {
+ override function select() {
+ var prev = checked;
+ super.select();
+ if (prev == checked) return;
+
TranslationUtil.setLanguage(value);
- if(FlxG.state is OptionsMenu) {
- var menu:OptionsMenu = cast FlxG.state;
- menu.reloadStrings();
- trace("Reloading strings");
- }
+ if (screen.parent == null) screen.reloadStrings();
+ else screen.parent.reloadStrings();
}
}
-class LanguageOptions extends OptionsScreen {
- public override function new(title:String, desc:String) {
- super(title, desc, "LanguageOptions.");
+class LanguageOptions extends TreeMenuScreen {
+ public override function new() {
+ super('optionsTree.language-name', 'optionsTree.language-desc', 'LanguageOptions.');
- var langArray:Array = TranslationUtil.foundLanguages;
-
- for(lang in langArray) {
+ for (lang in TranslationUtil.foundLanguages) {
var split = lang.split("/");
- var langId = split.first();
- var langName = split.last();
- var radio = new LanguageRadio(langName, langId);
- add(radio);
+ var langId = split.first(), langName = split.last();
+ add(new LanguageRadio(this, langName, langId));
}
}
-}
+}
\ No newline at end of file
diff --git a/source/funkin/options/categories/MiscOptions.hx b/source/funkin/options/categories/MiscOptions.hx
index 95b55aef..e72bafc8 100644
--- a/source/funkin/options/categories/MiscOptions.hx
+++ b/source/funkin/options/categories/MiscOptions.hx
@@ -1,35 +1,26 @@
package funkin.options.categories;
-class MiscOptions extends OptionsScreen {
- public override function new(title:String, desc:String) {
- super(title, desc, "MiscOptions.");
- add(new Checkbox(
- getName("devMode"),
- getDesc("devMode"),
- "devMode"));
+class MiscOptions extends TreeMenuScreen {
+ public function new() {
+ super('optionsTree.miscellaneous-name', 'optionsTree.miscellaneous-desc', 'MiscOptions.');
+
+ add(new Checkbox(getNameID('devMode'), getDescID('devMode'), 'devMode'));
+ add(new Checkbox(getNameID('allowConfigWarning'), getDescID('allowConfigWarning'), 'allowConfigWarning'));
#if UPDATE_CHECKING
- add(new Checkbox(
- getName("betaUpdates"),
- getDesc("betaUpdates"),
- "betaUpdates"));
- add(new TextOption(
- getName("checkForUpdates"),
- getDesc("checkForUpdates"),
- function() {
- var report = funkin.backend.system.updating.UpdateUtil.checkForUpdates();
- if (report.newUpdate) {
- FlxG.switchState(new funkin.backend.system.updating.UpdateAvailableScreen(report));
- } else {
- CoolUtil.playMenuSFX(CANCEL);
- updateDescText(TU.translate(prefix + "checkForUpdates-noUpdateFound"));
- }
+ add(new Checkbox(getNameID('betaUpdates'), getDescID('betaUpdates'), 'betaUpdates'));
+ add(new TextOption(getNameID('checkForUpdates'), getDescID('checkForUpdates'), () -> {
+ var report = funkin.backend.system.updating.UpdateUtil.checkForUpdates();
+ if (report.newUpdate) FlxG.switchState(new funkin.backend.system.updating.UpdateAvailableScreen(report));
+ else {
+ CoolUtil.playMenuSFX(CANCEL);
+ updateDescText(translate('checkForUpdates-noUpdateFound'));
+ }
}));
#end
- add(new TextOption(
- getName("resetSaveData"),
- getDesc("resetSaveData"),
- function() {
- // TODO: SAVE DATA RESETTING
+
+ add(new Separator());
+ add(new TextOption(getNameID('resetSaveData'), getDescID('resetSaveData'), () -> {
+ // TODO: SAVE DATA RESETTING
}));
}
-}
+}
\ No newline at end of file
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..52c33904 100644
--- a/source/funkin/options/type/ArrayOption.hx
+++ b/source/funkin/options/type/ArrayOption.hx
@@ -1,88 +1,64 @@
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;
+class ArrayOption extends TextOption {
+ public var changedCallback:String->Void;
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 __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);
- 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.optionName = optionName;
-
- add(__text = new Alphabet(100, 20, text, "bold"));
- add(__selectiontext = new Alphabet(__text.width + 120, 20, formatTextOption(), "normal"));
-
- rawText = text;
- }
-
- override function reloadStrings() {
- super.reloadStrings();
- this.rawText = rawText;
- }
-
- function set_rawText(v:String) {
- rawText = v;
- __text.text = TU.exists(rawText) ? TU.translate(rawText) : rawText;
+ override function set_text(v:String) {
+ super.set_text(v);
+ __selectionText.x = __text.x + __text.width + 12;
return v;
}
- public override function draw() {
- super.draw();
+ public function new(text:String, desc:String, options:Array, displayOptions:Array, ?optionName:String, ?changedCallback:Dynamic->Void = null, ?parent:Dynamic) {
+ this.changedCallback = changedCallback;
+ this.displayOptions = displayOptions;
+ this.options = options;
+ this.optionName = optionName;
+ this.parent = parent = parent != null ? parent : Options;
+
+ var fieldValue = Reflect.field(parent, optionName);
+ if (fieldValue != null) currentSelection = CoolUtil.maxInt(0, options.indexOf(fieldValue));
+
+ __selectionText = new Alphabet(0, 20, formatTextOption(), 'bold');
+ super(text, desc);
+ add(__selectionText);
}
- public override function onChangeSelection(change:Float):Void
- {
- if(currentSelection <= 0 && change == -1 || currentSelection >= options.length - 1 && change == 1) return;
- currentSelection += Math.round(change);
- __selectiontext.text = formatTextOption();
- Reflect.setField(parent, optionName, options[currentSelection]);
- if(selectCallback != null)
- selectCallback(options[currentSelection]);
+ override function reloadStrings() {
+ __selectionText.text = formatTextOption();
+ super.reloadStrings();
}
- private function formatTextOption() {
- var currentOptionString = ": ";
- if((currentSelection > 0))
- currentOptionString += "< ";
- else
- currentOptionString += " ";
+ function formatTextOption() {
+ var s = ": ";
- currentOptionString += displayOptions[currentSelection];
+ if (currentSelection > 0) s += "< ";
+ else s += " ";
- if(!(currentSelection >= options.length - 1))
- currentOptionString += " >";
+ s += TU.exists(displayOptions[currentSelection]) ? TU.translate(displayOptions[currentSelection]) : displayOptions[currentSelection];
- return currentOptionString;
+ if (currentSelection < options.length - 1) s += " >";
+
+ return s;
}
+
+ override function changeSelection(change:Int) {
+ if (locked || currentSelection == (currentSelection = CoolUtil.boundInt(currentSelection + change, 0, options.length - 1))) return;
+ __selectionText.text = formatTextOption();
+ CoolUtil.playMenuSFX(SCROLL);
+
+ if (optionName != null) Reflect.setField(parent, optionName, options[currentSelection]);
+ if (changedCallback != null) changedCallback(options[currentSelection]);
+ }
+
+ override function select() {}
}
\ No newline at end of file
diff --git a/source/funkin/options/type/Checkbox.hx b/source/funkin/options/type/Checkbox.hx
index 77947bd7..2e333420 100644
--- a/source/funkin/options/type/Checkbox.hx
+++ b/source/funkin/options/type/Checkbox.hx
@@ -10,25 +10,26 @@ class Checkbox extends TextOption {
public var checked(default, set):Bool;
public var parent:Dynamic;
-
public var optionName:String;
private var offsets:Map = [
"unchecked" => FlxPoint.get(0, -70),
- "checked" => FlxPoint.get(23, -30),
+ "checked" => FlxPoint.get(23, -32),
"unchecking" => FlxPoint.get(25, -12),
- "checking" => FlxPoint.get(35, 31)
+ "checking" => FlxPoint.get(35, 29)
];
- public function new(text:String, desc:String, optionName:String, ?parent:Dynamic) {
- super(text, desc, null);
+ override function set_text(v:String) {
+ super.set_text(v);
+ checkbox.x = __text.x + __text.width + 26;
+ return v;
+ }
- if (parent == null)
- parent = Options;
+ public function new(text:String, desc:String, optionName:String, ?selectCallback:Void->Void, ?parent:Dynamic) {
+ this.optionName = optionName;
+ this.parent = parent = parent != null ? parent : Options;
- this.parent = parent;
-
- checkbox = new FlxSprite(10, -40);
+ checkbox = new FlxSprite(10, -30);
checkbox.frames = Paths.getFrames('menus/options/checkboxThingie');
checkbox.animation.addByPrefix("unchecked", "Check Box unselected0", 24);
checkbox.animation.addByPrefix("checked", "Check Box Selected Static0", 24);
@@ -37,65 +38,59 @@ class Checkbox extends TextOption {
checkbox.antialiasing = true;
checkbox.scale.set(0.75, 0.75);
checkbox.updateHitbox();
- add(checkbox);
- this.optionName = optionName;
- if(optionName != null) {
- checked = Reflect.field(parent, optionName);
- } else {
- checked = false;
- }
+ //__text.x = 100;
+
+ if (optionName != null) checked = Reflect.field(parent, optionName);
+ else checked = false;
+
+ super(text, desc, selectCallback);
+ add(checkbox);
}
public var firstFrame:Bool = true;
- public override function update(elapsed:Float) {
- if(checkbox.animation.curAnim == null) {
- checkbox.animation.play(checked ? "checked" : "unchecked", true);
- }
+ override function update(elapsed:Float) {
+ if (checkbox.animation.curAnim == null) checkbox.animation.play(checked ? "checked" : "unchecked", true);
super.update(elapsed);
- switch(checkbox.animation.curAnim.name) {
- case "unchecking":
- if(checkbox.animation.curAnim.finished) {
- checkbox.animation.play("unchecked", true);
- }
- case "checking":
- if(checkbox.animation.curAnim.finished) {
- checkbox.animation.play("checked", true);
- }
+
+ if (checkbox.animation.curAnim.finished) switch(checkbox.animation.curAnim.name) {
+ case "unchecking": checkbox.animation.play("unchecked", true);
+ case "checking": checkbox.animation.play("checked", true);
}
firstFrame = false;
}
override function draw() {
- if(checkbox.animation.curAnim != null) {
+ if (checkbox.animation.curAnim != null) {
var offset = offsets[checkbox.animation.curAnim.name];
- if (offset != null)
- checkbox.frameOffset.set(offset.x, offset.y);
+ if (offset != null) checkbox.frameOffset.set(offset.x, offset.y);
}
super.draw();
}
function set_checked(v:Bool) {
- if(checked != v) {
- checked = v;
- if(!firstFrame) {
- checkbox.animation.play(checked ? "checking" : "unchecking", true);
- }
- }
+ if (checked == (checked = v)) return v;
+
+ if (!firstFrame) checkbox.animation.play(checked ? "checking" : "unchecking", true);
return v;
}
- public override function onSelect() {
+ override function select() {
+ if (locked) return;
+
checked = !checked;
- if(optionName != null) {
- Reflect.setField(parent, optionName, checked);
- }
+ if (optionName != null) Reflect.setField(parent, optionName, checked);
+
+ if (checked) CoolUtil.playMenuSFX(CHECKED);
+ else CoolUtil.playMenuSFX(UNCHECKED);
+
+ if (selectCallback != null) selectCallback();
}
- public override function destroy() {
+ override function destroy() {
super.destroy();
- for(e in offsets) e.put();
+ for (e in offsets) e.put();
}
}
\ No newline at end of file
diff --git a/source/funkin/options/type/EditorIconOption.hx b/source/funkin/options/type/EditorIconOption.hx
deleted file mode 100644
index 5303fb01..00000000
--- a/source/funkin/options/type/EditorIconOption.hx
+++ /dev/null
@@ -1,13 +0,0 @@
-package funkin.options.type;
-
-import flixel.util.FlxColor;
-
-class EditorIconOption extends IconOption {
- public var flashColor:FlxColor;
-
- public function new(name:String, desc:String, icon:String, callback:Void->Void, flashColor:FlxColor = 0) {
- super(name, desc, icon, callback);
-
- this.flashColor = flashColor;
- }
-}
\ No newline at end of file
diff --git a/source/funkin/options/type/GithubIconOption.hx b/source/funkin/options/type/GithubIconOption.hx
index be3567a8..16916b55 100644
--- a/source/funkin/options/type/GithubIconOption.hx
+++ b/source/funkin/options/type/GithubIconOption.hx
@@ -26,6 +26,7 @@ class GithubIconOption extends TextOption
this.icon = new GithubUserIcon(user, size, waitUntilLoad);
this.usePortrait = usePortrait;
add(icon);
+ __text.x = 100;
}
}
diff --git a/source/funkin/options/type/IconOption.hx b/source/funkin/options/type/IconOption.hx
index b61c5c67..547c9f59 100644
--- a/source/funkin/options/type/IconOption.hx
+++ b/source/funkin/options/type/IconOption.hx
@@ -12,6 +12,8 @@ class IconOption extends TextOption {
public function new(name:String, desc:String, icon:String, callback:Void->Void) {
super(name, desc, callback);
+ __text.x = 100;
+
iconSpr = new HealthIcon(icon, false);
iconSpr.setPosition(90 - iconSpr.width, (__text.height - iconSpr.height) / 2);
iconSpr.setUnstretchedGraphicSize(150, 150, true);
diff --git a/source/funkin/options/type/MeterOption.hx b/source/funkin/options/type/MeterOption.hx
index 7902e09e..831a4adc 100644
--- a/source/funkin/options/type/MeterOption.hx
+++ b/source/funkin/options/type/MeterOption.hx
@@ -20,11 +20,7 @@ class MeterOption extends OptionType {
public var barWidth(default, set):Int;
- private var rawText(default, set):String;
-
- public var text(get, set):String;
- private function get_text() {return __text.text;}
- private function set_text(v:String) {return __text.text = v;}
+ override function set_text(v:String) return __text.text = text = v;
private var __text:Alphabet;
var optionName:String;
@@ -43,7 +39,7 @@ class MeterOption extends OptionType {
public function new(text:String, desc:String, min:Float, max:Float, changeVal:Float, optionName:String, barWidth:Int = 520,
?selectCallback:Float->Void = null, ?parent:Dynamic)
{
- super(desc);
+ super(text, desc);
this.selectCallback = selectCallback;
this.min = min;
@@ -162,12 +158,6 @@ class MeterOption extends OptionType {
x += temp * barScale;
}
- function set_rawText(v:String) {
- rawText = v;
- __text.text = TU.exists(rawText) ? TU.translate(rawText) : rawText;
- return v;
- }
-
override function draw() {
lastX = x;
@@ -196,7 +186,7 @@ class MeterOption extends OptionType {
this.rawText = rawText;
}
- override function onChangeSelection(change:Float) {
+ override function changeSelection(change:Float) {
if (currentSelection <= min && change == -1 || currentSelection >= max && change == 1) return;
currentSelection = FlxMath.roundDecimal(currentSelection + (change * changeVal), FlxMath.getDecimals(changeVal));
diff --git a/source/funkin/options/type/NewOption.hx b/source/funkin/options/type/NewOption.hx
index 28255b8c..85181412 100644
--- a/source/funkin/options/type/NewOption.hx
+++ b/source/funkin/options/type/NewOption.hx
@@ -10,8 +10,10 @@ class NewOption extends TextOption {
public function new(name:String, desc:String, callback:Void->Void) {
super(name, desc, callback);
+ itemHeight = 160;
__text.color = FlxColor.LIME;
+ __text.x = 100;
iconSpr = new FlxSprite().loadGraphic(Paths.image("editors/new"));
iconSpr.setPosition(90 - iconSpr.width, (__text.height - iconSpr.height) / 2);
diff --git a/source/funkin/options/type/NumOption.hx b/source/funkin/options/type/NumOption.hx
index e570e197..953294ff 100644
--- a/source/funkin/options/type/NumOption.hx
+++ b/source/funkin/options/type/NumOption.hx
@@ -1,72 +1,53 @@
package funkin.options.type;
-import flixel.effects.FlxFlicker;
-
/**
* Option type that allows stepping through a number.
**/
-class NumOption extends OptionType {
- public var selectCallback:Float->Void;
-
- private var __text:Alphabet;
- private var __number:Alphabet;
+class NumOption extends TextOption {
+ public var changedCallback:Float->Void;
public var min:Float;
public var max:Float;
+ public var step:Float;
- public var currentSelection:Float;
- public var changeVal:Float;
-
- var optionName:String;
+ public var currentValue:Float;
public var parent:Dynamic;
+ public var optionName:String;
- private var rawText(default, set):String;
+ var __number: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, min:Float, max:Float, changeVal:Float, optionName:String, ?selectCallback:Float->Void = null, ?parent:Dynamic) {
- super(desc);
- this.selectCallback = selectCallback;
- this.min = min;
- this.max = max;
- if (parent == null)
- parent = Options;
-
- this.parent = parent;
-
- if(Reflect.field(parent, optionName) != null)
- this.currentSelection = Reflect.field(parent, optionName);
- this.changeVal = changeVal;
- this.optionName = optionName;
-
- add(__text = new Alphabet(20, 20, "", "bold"));
- add(__number = new Alphabet(0, 20, ': $currentSelection', "bold"));
- rawText = text;
- }
-
- override function reloadStrings() {
- super.reloadStrings();
- this.rawText = rawText;
- }
-
- function set_rawText(v:String) {
- rawText = v;
- __text.text = TU.exists(rawText) ? TU.translate(rawText) : rawText;
+ override function set_text(v:String) {
+ super.set_text(v);
__number.x = __text.x + __text.width + 12;
return v;
}
- override function onChangeSelection(change:Float):Void
- {
- if(currentSelection <= min && change == -1 || currentSelection >= max && change == 1) return;
- currentSelection = FlxMath.roundDecimal(currentSelection + (change * changeVal), FlxMath.getDecimals(changeVal));
- __number.text = ': $currentSelection';
+ public function new(text:String, desc:String, min:Float, max:Float, step:Float, optionName:String, ?changedCallback:Float->Void = null, ?parent:Dynamic) {
+ this.changedCallback = changedCallback;
+ this.min = min;
+ this.max = max;
+ this.step = step;
+ this.optionName = optionName;
+ this.parent = parent = parent != null ? parent : Options;
- Reflect.setField(parent, optionName, currentSelection);
- if(selectCallback != null)
- selectCallback(currentSelection);
+ if (Reflect.field(parent, optionName) != null) currentValue = Reflect.field(parent, optionName);
+
+ __number = new Alphabet(0, 20, ': $currentValue', 'bold');
+ super(text, desc);
+ add(__number);
}
+
+ override function changeSelection(change:Int):Void {
+ if (locked) return;
+ if (currentValue == (currentValue = FlxMath.bound(currentValue + change, min, max))) return;
+ __number.text = ': $currentValue';
+
+ Reflect.setField(parent, optionName, currentValue);
+ if (changedCallback != null) changedCallback(currentValue);
+
+ CoolUtil.playMenuSFX(SCROLL);
+ }
+
+ override function select() {}
}
\ No newline at end of file
diff --git a/source/funkin/options/type/OptionType.hx b/source/funkin/options/type/OptionType.hx
index 44cd94e1..3d4d25f0 100644
--- a/source/funkin/options/type/OptionType.hx
+++ b/source/funkin/options/type/OptionType.hx
@@ -1,41 +1,64 @@
package funkin.options.type;
import flixel.group.FlxSpriteGroup;
+import flixel.util.FlxColor;
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 locked(default, set):Bool;
+
+ 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 var itemHeight:Float = 120;
+
+ public var editorFlashColor:FlxColor = FlxColor.WHITE;
+
+ public function new(text:String, desc:String) {
super();
+ rawText = text;
rawDesc = desc;
}
+ function set_locked(v:Bool) {
+ if (locked == (locked = v)) return v;
+ color = locked ? 0xFF7F7F7F : 0xFFFFFFFF;
+ return v;
+ }
+
+ 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() {}
+
+ override function get_height() return itemHeight;
}
\ No newline at end of file
diff --git a/source/funkin/options/type/PortraitOption.hx b/source/funkin/options/type/PortraitOption.hx
index a719b32d..b512a8f2 100644
--- a/source/funkin/options/type/PortraitOption.hx
+++ b/source/funkin/options/type/PortraitOption.hx
@@ -12,6 +12,8 @@ class PortraitOption extends TextOption {
public function new(name:String, desc:String, callback:Void->Void, ?graphic:FlxGraphic, size:Int = 96, usePortrait:Bool = true) {
super(name, desc, callback);
+ __text.x = 100;
+
if (graphic != null) addPortrait(graphic, size, usePortrait);
}
diff --git a/source/funkin/options/type/RadioButton.hx b/source/funkin/options/type/RadioButton.hx
index d43240ac..b576c192 100644
--- a/source/funkin/options/type/RadioButton.hx
+++ b/source/funkin/options/type/RadioButton.hx
@@ -1,17 +1,19 @@
package funkin.options.type;
import flixel.math.FlxPoint;
+import funkin.options.TreeMenuScreen;
class RadioButton extends TextOption {
public var radio:FlxSprite;
public var checked(default, set):Bool;
public var parent:Dynamic;
- public var value:Dynamic;
- public var forId:String;
-
public var optionName:String;
+ public var screen:TreeMenuScreen;
+ public var forId:String;
+ public var value:Dynamic;
+
private var offsets:Map = [
"unchecked" => FlxPoint.get(0, -65),
"checked" => FlxPoint.get(0, -65),
@@ -19,26 +21,15 @@ class RadioButton extends TextOption {
"checking" => FlxPoint.get(17, -40)
];
- public function new(text:String, desc:String, optionName:String, value:Dynamic, ?parent:Dynamic, ?forId:String, ?options:OptionsScreen) {
- super(text, desc, null);
+ public function new(?screen:TreeMenuScreen, text:String, desc:String, optionName:String, value:Dynamic, ?selectCallback:Void->Void, ?parent:Dynamic, ?forId:String) {
+ super(text, desc, selectCallback);
+ this.screen = screen;
+ this.optionName = optionName;
+ this.parent = parent = parent != null ? parent : Options;
+ this.forId = forId = forId == null ? optionName : forId;
+ this.value = value;
- if (parent == null)
- parent = Options;
-
- if(forId == null)
- forId = optionName;
-
- this.parent = parent;
- this.forId = forId;
-
- if(options != null) {
- if(FlxG.state is OptionsMenu) {
- var menu:OptionsMenu = cast FlxG.state;
- options = menu.optionsTree.members.last();
- }
- }
-
- radio = new FlxSprite(10, -40);
+ radio = new FlxSprite(10, -23);
radio.frames = Paths.getFrames('menus/options/radioCrank');
radio.animation.addByPrefix("unchecked", "Radio unselected0", 24);
radio.animation.addByPrefix("checked", "Radio Selected Static0", 24);
@@ -49,73 +40,58 @@ class RadioButton extends TextOption {
radio.updateHitbox();
add(radio);
- this.value = value;
+ __text.x = 100;
- this.optionName = optionName;
- if(optionName != null) {
- checked = Reflect.field(parent, optionName) == value;
- } else {
- checked = false;
- }
+ if (optionName != null) checked = Reflect.field(parent, optionName) == value;
+ else checked = false;
}
public var firstFrame:Bool = true;
- public override function update(elapsed:Float) {
- if(radio.animation.curAnim == null) {
- radio.animation.play(checked ? "checked" : "unchecked", true);
- }
+ override function update(elapsed:Float) {
+ if (radio.animation.curAnim == null) radio.animation.play(checked ? "checked" : "unchecked", true);
super.update(elapsed);
- switch(radio.animation.curAnim.name) {
- case "unchecking":
- if(radio.animation.curAnim.finished) {
- radio.animation.play("unchecked", true);
- trace("UNCHECKING finished");
- }
- case "checking":
- if(radio.animation.curAnim.finished) {
- radio.animation.play("checked", true);
- trace("CHECKING finished");
- }
+
+ if (radio.animation.curAnim.finished) switch(radio.animation.curAnim.name) {
+ case "unchecking": radio.animation.play("unchecked", true);
+ case "checking": radio.animation.play("checked", true);
}
firstFrame = false;
}
override function draw() {
- if(radio.animation.curAnim != null) {
+ if (radio.animation.curAnim != null) {
var offset = offsets[radio.animation.curAnim.name];
- if (offset != null)
- radio.frameOffset.set(offset.x, offset.y);
+ if (offset != null) radio.frameOffset.set(offset.x, offset.y);
}
super.draw();
}
function set_checked(v:Bool) {
- if(checked != v) {
- checked = v;
- if(!firstFrame) {
- radio.animation.play(checked ? "checking" : "unchecking", true);
- }
- }
+ if (checked == (checked = v)) return v;
+
+ if (!firstFrame) radio.animation.play(checked ? "checking" : "unchecking", true);
return v;
}
- public override function onSelect() {
+ override function select() {
+ if (locked) return;
+
checked = true;
- if(checked) {
- onSet(value);
+ if (optionName != null) Reflect.setField(parent, optionName, value);
+
+ CoolUtil.playMenuSFX(CHECKED);
+
+ if (screen != null) {
+ for (item in screen.members) if (item != null && item != this && item is RadioButton) cast(item, RadioButton).checked = false;
}
+
+ if (selectCallback != null) selectCallback();
}
- public dynamic function onSet(value:Dynamic) {
- if(optionName != null) {
- Reflect.setField(parent, optionName, value);
- }
- }
-
- public override function destroy() {
+ override function destroy() {
super.destroy();
- for(e in offsets) e.put();
+ for (e in offsets) e.put();
}
}
\ No newline at end of file
diff --git a/source/funkin/options/type/Separator.hx b/source/funkin/options/type/Separator.hx
new file mode 100644
index 00000000..716c3576
--- /dev/null
+++ b/source/funkin/options/type/Separator.hx
@@ -0,0 +1,30 @@
+package funkin.options.type;
+
+import flixel.math.FlxPoint;
+import flixel.util.FlxDestroyUtil;
+import flixel.FlxBasic;
+
+class Separator extends FlxSprite {
+ var separatorHeight:Float;
+ public function new(height = 67) {
+ super();
+ separatorHeight = height;
+ }
+
+ override function initVars() {
+ flixelType = OBJECT;
+
+ offset = FlxPoint.get();
+ origin = FlxPoint.get();
+ scale = FlxPoint.get(1, 1);
+ scrollFactor = FlxPoint.get(1, 1);
+
+ initMotionVars();
+ }
+
+ override function update(elapsed:Float) {}
+ override function draw() {}
+
+ override function get_height():Float return separatorHeight;
+ override function get_width():Float return 600;
+}
\ 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..955b61d9
--- /dev/null
+++ b/source/funkin/options/type/SliderOption.hx
@@ -0,0 +1,88 @@
+package funkin.options.type;
+
+import funkin.menus.ui.Slider;
+import funkin.options.TreeMenu.ITreeFloatOption;
+
+class SliderOption extends TextOption implements ITreeFloatOption {
+ public var changedCallback:Float->Void;
+
+ public var min:Float;
+ public var max:Float;
+ public var step:Float;
+
+ public var currentValue:Float;
+
+ public var parent:Dynamic;
+ public var optionName:String;
+
+ public var slider:Slider;
+ public var dynamicWidth:Bool;
+
+ var __mouseControl:Bool;
+
+ function getValue():Float return (currentValue - min) / (max - min);
+
+ override function set_text(v:String) {
+ super.set_text(v);
+ slider.x = __text.x + __text.width + 30;
+ if (dynamicWidth) {
+ slider.barWidth = 1100 - __text.width;
+ slider.updateHitbox();
+ }
+ return v;
+ }
+
+ public function new(text:String, desc:String, min:Float, max:Float, step:Float, ?segments:Int, optionName:String, barWidth = -1,
+ ?changedCallback:Float->Void = null, ?parent:Dynamic)
+ {
+ this.changedCallback = changedCallback;
+ this.min = min;
+ this.max = max;
+ this.step = step;
+ this.optionName = optionName;
+ this.parent = parent = parent != null ? parent : Options;
+
+ if (Reflect.field(parent, optionName) != null) currentValue = Reflect.field(parent, optionName);
+
+ slider = new Slider(12, 0, getValue(), (dynamicWidth = barWidth < 0) ? 600 : barWidth, segments);
+ slider.scale.set(0.75, 0.75);
+ slider.updateHitbox();
+
+ super(text, desc);
+ add(slider);
+ slider.y = Math.floor(__text.y + (__text.height - slider.height) * 0.5 + 2);
+ }
+
+ override function update(elapsed:Float) {
+ if (slider.selected = selected && !locked) {
+ if (__mouseControl && FlxG.mouse.justReleased) __mouseControl = false;
+ else if (FlxG.mouse.justPressed) __mouseControl = slider.overlapsPoint(FlxG.mouse.getPosition(@:privateAccess flixel.input.FlxPointer._cachedPoint), true);
+
+ if (__mouseControl) {
+ var p = @:privateAccess flixel.input.FlxPointer._cachedPoint;
+ if (!FlxG.mouse.justPressed) FlxG.mouse.getPosition(p);
+ p.subtractPoint(camera.scroll);
+
+ slider.getScreenPosition(_point, camera);
+ changeValue((FlxMath.remapToRange(p.x - _point.x, 0, slider.width, min, max) - currentValue) / step);
+ }
+ }
+ else
+ __mouseControl = false;
+
+ slider.value = getValue();
+ //__text.x = slider.x + slider.width + 30;
+
+ super.update(elapsed);
+ }
+
+ public function changeValue(change:Float):Void {
+ if (locked) return;
+ if (currentValue == (currentValue = FlxMath.bound(currentValue + change * step, min, max))) return;
+
+ Reflect.setField(parent, optionName, currentValue);
+ if (changedCallback != null) changedCallback(currentValue);
+ }
+
+ override function select() {}
+}
\ No newline at end of file
diff --git a/source/funkin/options/type/TextOption.hx b/source/funkin/options/type/TextOption.hx
index 2d2b5187..64b93b25 100644
--- a/source/funkin/options/type/TextOption.hx
+++ b/source/funkin/options/type/TextOption.hx
@@ -6,46 +6,35 @@ import flixel.effects.FlxFlicker;
* Option type that has text.
**/
class TextOption extends OptionType {
+ public var suffix(default, set):String;
public var selectCallback:Void->Void;
- private var __text:Alphabet;
- private var rawText(default, set):String;
-
- public var text(get, set):String;
- private function get_text() {return __text.text;}
- private function set_text(v:String) {return __text.text = v;}
-
- /**
- * If you change this afterwards you will need to call `reloadStrings()`, or manually set rawText to rawText.
- **/
- public var suffix:String;
-
- public function new(text:String, desc:String, ?suffix:String = "", ?selectCallback:Void->Void = null) {
- super(desc);
- this.selectCallback = selectCallback;
- add(__text = new Alphabet(100, 20, "", "bold"));
- this.suffix = suffix;
- rawText = text;
+ var __text:Alphabet;
+ override function set_text(v:String) {
+ __text.text = v + suffix;
+ return text = v;
}
- override function reloadStrings() {
- super.reloadStrings();
- this.rawText = rawText;
- }
-
- function set_rawText(v:String) {
- rawText = v;
- __text.text = (TU.exists(rawText) ? TU.translate(rawText) : rawText) + suffix;
+ function set_suffix(v:String) {
+ if (suffix == (suffix = v)) return v;
+ __text.text = text + suffix;
return v;
}
- public override function draw() {
- super.draw();
+ public function new(text:String, desc:String, ?suffix:String = "", ?selectCallback:Void->Void = null) {
+ @:bypassAccessor this.suffix = suffix;
+ this.selectCallback = selectCallback;
+
+ __text = new Alphabet(20, 20, "", "bold");
+
+ super(text, desc);
+ add(__text);
}
- public override function onSelect() {
- super.onSelect();
+
+ override function select() {
+ if (locked) return;
CoolUtil.playMenuSFX(CONFIRM);
FlxFlicker.flicker(this, 1, Options.flashingMenu ? 0.06 : 0.15, true, false);
- selectCallback();
+ if (selectCallback != null) selectCallback();
}
}
\ No newline at end of file