diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index 47510764..175e0eb0 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -528,7 +528,7 @@ final class CoolUtil case UNCHECKED: 'checkboxUnchecked'; case WARNING: 'warningMenu'; default: 'scroll'; - }), volume); + }), volume * Options.volumeSFX); } /** diff --git a/source/funkin/menus/ui/Slider.hx b/source/funkin/menus/ui/Slider.hx index 45360ce7..c60a740c 100644 --- a/source/funkin/menus/ui/Slider.hx +++ b/source/funkin/menus/ui/Slider.hx @@ -1,5 +1,116 @@ package funkin.menus.ui; +import flixel.animation.FlxAnimation; +import flixel.graphics.frames.FlxFramesCollection; +import flixel.graphics.frames.FlxFrame; +import flixel.math.FlxPoint; + class Slider extends FlxSprite { - + public var playSound:Bool = true; + public var selected:Bool = false; + public var value:Float; + public var segments: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 __curSegments:Int; + + public function new(?x:Float, ?y:Float, value:Float = 0.5, barWidth:Float = 500, segments:Int = 4) { + super(x, y); + this.value = value; + this.barWidth = barWidth; + this.segments = segments; + + frames = Paths.getFrames('menus/options/slider'); + } + + 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); + __animTime += elapsed; + + 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': 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); + } + } + } + + override function isSimpleRender(?camera:FlxCamera):Bool return false; + override function drawComplex(camera:FlxCamera) { + if (showSlider) { + var xOff = frame.sourceSize.x * 0.5 - barWidth * value, yOff = (frame.sourceSize.y - barHeight) * 0.5; + frameOffset.add(xOff, yOff); + super.drawComplex(camera); + frameOffset.subtract(xOff, yOff); + } + } + + 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); + + var anim; + barHeight = 0; + for (type in 0...3) { + if ((anim = getBarAnim(type, false)) != null) barHeight = Math.max(barHeight, frames.frames[anim.frames[0]].sourceSize.y); + if ((anim = getBarAnim(type, true)) != null) barHeight = Math.max(barHeight, frames.frames[anim.frames[0]].sourceSize.y); + } + + sliderResetFrameSize(); + resetSizeFromFrame(); + centerOrigin(); + + if (FlxG.renderBlit) { + dirty = true; + updateFramePixels(); + } + } + + override function set_frame(v:FlxFrame):FlxFrame { + super.set_frame(v); + sliderResetFrameSize(); + return v; + } + + function set_barWidth(v:Float):Float { + origin.x = _halfSize.x = 0.5 * (frameWidth = Std.int(barWidth = v)); + return v; + } } \ No newline at end of file diff --git a/source/funkin/options/TreeMenu.hx b/source/funkin/options/TreeMenu.hx index f5c0e177..33ae47c6 100644 --- a/source/funkin/options/TreeMenu.hx +++ b/source/funkin/options/TreeMenu.hx @@ -12,11 +12,14 @@ interface ITreeOption { var desc:String; var selected:Bool; - function reloadStrings():Void; function changeSelection(change:Int):Void; function select():Void; } +interface ITreeFloatOption extends ITreeOption { + function changeValue(change:Float):Void; +} + class TreeMenu extends UIState { public var onMenuClosed:FlxTypedSignalVoid> = new FlxTypedSignal(); public var onMenuChanged:FlxTypedSignalVoid> = new FlxTypedSignal(); diff --git a/source/funkin/options/TreeMenuScreen.hx b/source/funkin/options/TreeMenuScreen.hx index 17b012e1..3bec7899 100644 --- a/source/funkin/options/TreeMenuScreen.hx +++ b/source/funkin/options/TreeMenuScreen.hx @@ -5,6 +5,8 @@ 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 { @@ -55,6 +57,7 @@ class TreeMenuScreen extends FlxSpriteGroup { var turboBasics:Array; var curOption:ITreeOption; + var curFloatOption:ITreeFloatOption; var __firstFrame:Bool = true; public function new(name:String, desc:String, ?prefix:String, ?objects:Array) { @@ -72,7 +75,7 @@ class TreeMenuScreen extends FlxSpriteGroup { rawName = rawName; rawDesc = rawDesc; - for (object in members) if (object != null && object is ITreeOption) cast(object, ITreeOption).reloadStrings(); + for (object in members) if (object != null && object is OptionType) cast(object, OptionType).reloadStrings(); } override function update(elapsed:Float) { @@ -80,7 +83,10 @@ class TreeMenuScreen extends FlxSpriteGroup { if (__firstFrame) { __firstFrame = false; - if (members[curSelected] is ITreeOption) (curOption = cast members[curSelected]).selected = true; + if (members[curSelected] is ITreeOption) { + (curOption = cast members[curSelected]).selected = true; + if (curOption is ITreeFloatOption) curFloatOption = cast curOption; + } updateItems(true); return; } @@ -91,8 +97,14 @@ class TreeMenuScreen extends FlxSpriteGroup { if (length > 0 && curOption != null) { if (controls.ACCEPT || (FlxG.mouse.justPressed && FlxG.mouse.overlaps(members[curSelected]))) curOption.select(); - if (leftTurboControl.activated) curOption.changeSelection(-1); - if (rightTurboControl.activated) curOption.changeSelection(1); + 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(); @@ -142,7 +154,15 @@ class TreeMenuScreen extends FlxSpriteGroup { 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 (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); diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx index 3faa11db..219ed322 100644 --- a/source/funkin/options/categories/GameplayOptions.hx +++ b/source/funkin/options/categories/GameplayOptions.hx @@ -14,8 +14,9 @@ class GameplayOptions extends TreeMenuScreen { 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')); + 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('volumeSFX'), getDescID('volumeSFX'), 0, 1, 1, 10, 'volumeSFX')); add(new Separator()); add(new TextOption('optionsMenu.advanced', 'optionsTree.gameplay.advanced-desc', ' >', () -> @@ -23,6 +24,7 @@ class GameplayOptions extends TreeMenuScreen { } private function __changeOffset(offset:Float) Conductor.songOffset = offset; + private function __changeAutoPause() FlxG.autoPause = Options.autoPause; var __lastBeat:Int = 0; var __lastSongBeat:Int = 0; diff --git a/source/funkin/options/type/NumOption.hx b/source/funkin/options/type/NumOption.hx index 6590afd9..953294ff 100644 --- a/source/funkin/options/type/NumOption.hx +++ b/source/funkin/options/type/NumOption.hx @@ -10,7 +10,7 @@ class NumOption extends TextOption { public var max:Float; public var step:Float; - public var currentSelection:Float; + public var currentValue:Float; public var parent:Dynamic; public var optionName:String; @@ -31,20 +31,20 @@ class NumOption extends TextOption { this.optionName = optionName; this.parent = parent = parent != null ? parent : Options; - if (Reflect.field(parent, optionName) != null) currentSelection = Reflect.field(parent, optionName); + if (Reflect.field(parent, optionName) != null) currentValue = Reflect.field(parent, optionName); - __number = new Alphabet(0, 20, ': $currentSelection', 'bold'); + __number = new Alphabet(0, 20, ': $currentValue', 'bold'); super(text, desc); add(__number); } override function changeSelection(change:Int):Void { if (locked) return; - if (currentSelection == (currentSelection = FlxMath.bound(currentSelection + change, min, max))) return; - __number.text = ': $currentSelection'; + if (currentValue == (currentValue = FlxMath.bound(currentValue + change, min, max))) return; + __number.text = ': $currentValue'; - Reflect.setField(parent, optionName, currentSelection); - if (changedCallback != null) changedCallback(currentSelection); + Reflect.setField(parent, optionName, currentValue); + if (changedCallback != null) changedCallback(currentValue); CoolUtil.playMenuSFX(SCROLL); } diff --git a/source/funkin/options/type/SliderOption.hx b/source/funkin/options/type/SliderOption.hx index 5831a47d..ed0ce5b8 100644 --- a/source/funkin/options/type/SliderOption.hx +++ b/source/funkin/options/type/SliderOption.hx @@ -1,26 +1,58 @@ package funkin.options.type; -class SliderOption extends OptionType { - public var selectCallback:Float->Void; +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 segmentVal:Float; + public var step:Float; - public var currentSelection:Float; + public var currentValue:Float; public var parent:Dynamic; + public var optionName:String; - //public var barWidth(get, set):Int; + public var slider:Slider; - override function set_text(v:String) return __text.text = text = v; + function getValue():Float return (currentValue - min) / (max - min); - var __text:Alphabet; - var optionName:String; - - public function new(text:String, desc:String, min:Float, max:Float, segmentVal:Float, optionName:String, barWidth:Int = 520, - ?selectCallback:Float->Void = null, ?parent:Dynamic) + public function new(text:String, desc:String, min:Float, max:Float, step:Float, ?segments:Int, optionName:String, barWidth = 600.0, + ?changedCallback:Float->Void = null, ?parent:Dynamic) { super(text, desc); + 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); + + add(slider = new Slider(16, 0, getValue(), barWidth, segments)); + slider.scale.set(0.75, 0.75); + slider.updateHitbox(); + slider.y = __text.y + (__text.height - slider.height) * 0.5; } + + override function update(elapsed:Float) { + slider.selected = selected && !locked; + 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