WIP Slider

This commit is contained in:
Ralty
2025-07-27 13:51:27 +07:00
parent 33b346f17d
commit f9d94bbebd
7 changed files with 195 additions and 27 deletions
+1 -1
View File
@@ -528,7 +528,7 @@ final class CoolUtil
case UNCHECKED: 'checkboxUnchecked';
case WARNING: 'warningMenu';
default: 'scroll';
}), volume);
}), volume * Options.volumeSFX);
}
/**
+112 -1
View File
@@ -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;
}
}
+4 -1
View File
@@ -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:FlxTypedSignal<TreeMenuScreen->Void> = new FlxTypedSignal();
public var onMenuChanged:FlxTypedSignal<TreeMenuScreen->Void> = new FlxTypedSignal();
+25 -5
View File
@@ -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<TurboBasic>;
var curOption:ITreeOption;
var curFloatOption:ITreeFloatOption;
var __firstFrame:Bool = true;
public function new(name:String, desc:String, ?prefix:String, ?objects:Array<FlxSprite>) {
@@ -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);
@@ -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;
+7 -7
View File
@@ -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);
}
+43 -11
View File
@@ -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() {}
}