mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-26 01:28:16 -05:00
Numerous chart editor fixes.
This commit is contained in:
parent
20e6c7a2be
commit
fe92d00a04
10 changed files with 575 additions and 367 deletions
|
@ -193,7 +193,8 @@
|
|||
{
|
||||
"props": {
|
||||
"max": 1000,
|
||||
"ignoreEmptyLines": true
|
||||
"ignoreEmptyLines": true,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "FileLength"
|
||||
},
|
||||
|
@ -232,7 +233,7 @@
|
|||
},
|
||||
{
|
||||
"props": {
|
||||
"ignoreReturnAssignments": false,
|
||||
"ignoreReturnAssignments": true,
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "InnerAssignment"
|
||||
|
@ -392,12 +393,13 @@
|
|||
},
|
||||
{
|
||||
"props": {
|
||||
"oldFunctionTypePolicy": "none",
|
||||
"unaryOpPolicy": "none",
|
||||
"intervalOpPolicy": "none",
|
||||
|
||||
"newFunctionTypePolicy": "around",
|
||||
"ternaryOpPolicy": "around",
|
||||
"unaryOpPolicy": "none",
|
||||
"oldFunctionTypePolicy": "around",
|
||||
"boolOpPolicy": "around",
|
||||
"intervalOpPolicy": "none",
|
||||
"assignOpPolicy": "around",
|
||||
"bitwiseOpPolicy": "around",
|
||||
"arithmeticOpPolicy": "around",
|
||||
|
@ -623,7 +625,9 @@
|
|||
"type": "UnusedImport"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"props": {
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "UnusedLocalVar"
|
||||
},
|
||||
{
|
||||
|
|
4
hmm.json
4
hmm.json
|
@ -42,14 +42,14 @@
|
|||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "e5cf78d",
|
||||
"ref": "59157d2",
|
||||
"url": "https://github.com/haxeui/haxeui-core/"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f03bb6d",
|
||||
"ref": "d353389",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
|
|
116
source/funkin/input/TurboKeyHandler.hx
Normal file
116
source/funkin/input/TurboKeyHandler.hx
Normal file
|
@ -0,0 +1,116 @@
|
|||
package funkin.input;
|
||||
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.FlxBasic;
|
||||
|
||||
/**
|
||||
* Handles repeating behavior when holding down a key or key combination.
|
||||
*
|
||||
* When the `keys` are pressed, `activated` will be true for the first frame,
|
||||
* then wait `delay` seconds before becoming true for one frame every `interval` seconds.
|
||||
*
|
||||
* Example: Pressing Ctrl+Z will undo, while holding Ctrl+Z will start to undo repeatedly.
|
||||
*/
|
||||
class TurboKeyHandler extends FlxBasic
|
||||
{
|
||||
/**
|
||||
* Default delay before repeating.
|
||||
*/
|
||||
static inline final DEFAULT_DELAY:Float = 0.4;
|
||||
|
||||
/**
|
||||
* Default interval between repeats.
|
||||
*/
|
||||
static inline final DEFAULT_INTERVAL:Float = 0.1;
|
||||
|
||||
/**
|
||||
* Whether all of the keys for this handler are pressed.
|
||||
*/
|
||||
public var allPressed(get, null):Bool;
|
||||
|
||||
/**
|
||||
* Whether all of the keys for this handler are activated,
|
||||
* and the handler is ready to repeat.
|
||||
*/
|
||||
public var activated(default, null):Bool = false;
|
||||
|
||||
var keys:Array<FlxKey>;
|
||||
var delay:Float;
|
||||
var interval:Float;
|
||||
|
||||
var allPressedTime:Float = 0;
|
||||
|
||||
function new(keys:Array<FlxKey>, delay:Float = DEFAULT_DELAY, interval:Float = DEFAULT_INTERVAL)
|
||||
{
|
||||
super();
|
||||
this.keys = keys;
|
||||
this.delay = delay;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
function get_allPressed():Bool
|
||||
{
|
||||
if (keys == null || keys.length == 0) return false;
|
||||
if (keys.length == 1) return FlxG.keys.anyPressed(keys);
|
||||
|
||||
// Check if ANY keys are unpressed
|
||||
for (key in keys)
|
||||
{
|
||||
if (!FlxG.keys.anyPressed([key])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (allPressed)
|
||||
{
|
||||
if (allPressedTime == 0)
|
||||
{
|
||||
activated = true;
|
||||
}
|
||||
else if (allPressedTime >= (delay + interval))
|
||||
{
|
||||
activated = true;
|
||||
allPressedTime -= interval;
|
||||
}
|
||||
else
|
||||
{
|
||||
activated = false;
|
||||
}
|
||||
allPressedTime += elapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
allPressedTime = 0;
|
||||
activated = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a TurboKeyHandler that monitors from a single key.
|
||||
* @param inputKey The key to monitor.
|
||||
* @param delay How long to wait before repeating.
|
||||
* @param repeatDelay How long to wait between repeats.
|
||||
* @return A TurboKeyHandler
|
||||
*/
|
||||
public static overload inline extern function build(inputKey:FlxKey, ?delay:Float = DEFAULT_DELAY, ?interval:Float = DEFAULT_INTERVAL):TurboKeyHandler
|
||||
{
|
||||
return new TurboKeyHandler([inputKey], delay, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a TurboKeyHandler that monitors a key combination.
|
||||
* @param inputKeys The combination of keys to monitor.
|
||||
* @param delay How long to wait before repeating.
|
||||
* @param repeatDelay How long to wait between repeats.
|
||||
* @return A TurboKeyHandler
|
||||
*/
|
||||
public static overload inline extern function build(inputKeys:Array<FlxKey>, ?delay:Float = DEFAULT_DELAY,
|
||||
?interval:Float = DEFAULT_INTERVAL):TurboKeyHandler
|
||||
{
|
||||
return new TurboKeyHandler(inputKeys, delay, interval);
|
||||
}
|
||||
}
|
|
@ -17,8 +17,7 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
|||
|
||||
function set_active(value:Bool):Bool
|
||||
{
|
||||
this.active = value;
|
||||
return value;
|
||||
return this.active = value;
|
||||
}
|
||||
|
||||
public var moduleId(default, null):String = 'UNKNOWN';
|
||||
|
|
|
@ -436,22 +436,19 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
if (!exists || x == value) return x; // early return (no need to transform)
|
||||
|
||||
transformChildren(xTransform, value - x); // offset
|
||||
x = value;
|
||||
return x;
|
||||
return x = value;
|
||||
}
|
||||
|
||||
override function set_y(value:Float):Float
|
||||
{
|
||||
if (exists && y != value) transformChildren(yTransform, value - y); // offset
|
||||
y = value;
|
||||
return y;
|
||||
return y = value;
|
||||
}
|
||||
|
||||
override function set_angle(value:Float):Float
|
||||
{
|
||||
if (exists && angle != value) transformChildren(angleTransform, value - angle); // offset
|
||||
angle = value;
|
||||
return angle;
|
||||
return angle = value;
|
||||
}
|
||||
|
||||
override function set_alpha(value:Float):Float
|
||||
|
@ -462,43 +459,37 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
{
|
||||
transformChildren(directAlphaTransform, value);
|
||||
}
|
||||
alpha = value;
|
||||
return alpha;
|
||||
return alpha = value;
|
||||
}
|
||||
|
||||
override function set_facing(value:Int):Int
|
||||
{
|
||||
if (exists && facing != value) transformChildren(facingTransform, value);
|
||||
facing = value;
|
||||
return facing;
|
||||
return facing = value;
|
||||
}
|
||||
|
||||
override function set_flipX(value:Bool):Bool
|
||||
{
|
||||
if (exists && flipX != value) transformChildren(flipXTransform, value);
|
||||
flipX = value;
|
||||
return flipX;
|
||||
return flipX = value;
|
||||
}
|
||||
|
||||
override function set_flipY(value:Bool):Bool
|
||||
{
|
||||
if (exists && flipY != value) transformChildren(flipYTransform, value);
|
||||
flipY = value;
|
||||
return flipY;
|
||||
return flipY = value;
|
||||
}
|
||||
|
||||
override function set_moves(value:Bool):Bool
|
||||
{
|
||||
if (exists && moves != value) transformChildren(movesTransform, value);
|
||||
moves = value;
|
||||
return moves;
|
||||
return moves = value;
|
||||
}
|
||||
|
||||
override function set_immovable(value:Bool):Bool
|
||||
{
|
||||
if (exists && immovable != value) transformChildren(immovableTransform, value);
|
||||
immovable = value;
|
||||
return immovable;
|
||||
return immovable = value;
|
||||
}
|
||||
|
||||
override function set_solid(value:Bool):Bool
|
||||
|
@ -510,15 +501,13 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
override function set_color(value:Int):Int
|
||||
{
|
||||
if (exists && color != value) transformChildren(gColorTransform, value);
|
||||
color = value;
|
||||
return color;
|
||||
return color = value;
|
||||
}
|
||||
|
||||
override function set_blend(value:BlendMode):BlendMode
|
||||
{
|
||||
if (exists && blend != value) transformChildren(blendTransform, value);
|
||||
blend = value;
|
||||
return blend;
|
||||
return blend = value;
|
||||
}
|
||||
|
||||
override function set_clipRect(rect:FlxRect):FlxRect
|
||||
|
|
|
@ -126,8 +126,10 @@ class Song // implements IPlayStateScriptedClass
|
|||
|
||||
/**
|
||||
* Retrieve the metadata for a specific difficulty, including the chart if it is loaded.
|
||||
* @param diffId The difficulty ID, such as `easy` or `hard`.
|
||||
* @return The difficulty data.
|
||||
*/
|
||||
public inline function getDifficulty(?diffId:String):SongDifficulty
|
||||
public inline function getDifficulty(diffId:String = null):SongDifficulty
|
||||
{
|
||||
if (diffId == null) diffId = difficulties.keys().array()[0];
|
||||
|
||||
|
|
|
@ -70,8 +70,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
|
||||
this.x += xDiff;
|
||||
this.y += yDiff;
|
||||
globalOffsets = value;
|
||||
return value;
|
||||
return globalOffsets = value;
|
||||
}
|
||||
|
||||
var animOffsets(default, set):Array<Float> = [0, 0];
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import haxe.io.Path;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.play.song.SongData.SongPlayableChar;
|
||||
import funkin.play.song.SongData.SongTimeChange;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.Image;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.Link;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
|
@ -23,8 +21,10 @@ import haxe.ui.containers.dialogs.Dialogs;
|
|||
import haxe.ui.containers.properties.PropertyGrid;
|
||||
import haxe.ui.containers.properties.PropertyGroup;
|
||||
import haxe.ui.containers.VBox;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
|
||||
using Lambda;
|
||||
|
||||
|
@ -43,7 +43,9 @@ class ChartEditorDialogHandler
|
|||
static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide');
|
||||
|
||||
/**
|
||||
*
|
||||
* Builds and opens a dialog giving brief credits for the chart editor.
|
||||
* @param state The current chart editor state.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static inline function openAboutDialog(state:ChartEditorState):Dialog
|
||||
{
|
||||
|
@ -52,72 +54,70 @@ class ChartEditorDialogHandler
|
|||
|
||||
/**
|
||||
* Builds and opens a dialog letting the user create a new chart, open a recent chart, or load from a template.
|
||||
* @param state The current chart editor state.
|
||||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static function openWelcomeDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
||||
{
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable);
|
||||
|
||||
// TODO: Add callbacks to the dialog buttons
|
||||
|
||||
// Add handlers to the "Create From Song" section.
|
||||
var linkCreateBasic:Link = dialog.findComponent('splashCreateFromSongBasic', Link);
|
||||
linkCreateBasic.onClick = (_event) -> {
|
||||
linkCreateBasic.onClick = function(_event) {
|
||||
// Hide the welcome dialog
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Create song wizard
|
||||
var uploadInstDialog = openUploadInstDialog(state, false);
|
||||
uploadInstDialog.onDialogClosed = (_event) -> {
|
||||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
|
||||
// Step 1. Upload Instrumental
|
||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, false);
|
||||
uploadInstDialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
var songMetadataDialog = openSongMetadataDialog(state);
|
||||
songMetadataDialog.onDialogClosed = (_event) -> {
|
||||
// Step 2. Song Metadata
|
||||
var songMetadataDialog:Dialog = openSongMetadataDialog(state);
|
||||
songMetadataDialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
var uploadVocalsDialog = openUploadVocalsDialog(state, false);
|
||||
// Step 3. Upload Vocals
|
||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||
openUploadVocalsDialog(state, false); // var uploadVocalsDialog:Dialog
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Get the list of songs and insert them as links into the "Create From Song" section.
|
||||
|
||||
/*
|
||||
var linkTemplateDadBattle:Link = dialog.findComponent('splashTemplateDadBattle', Link);
|
||||
linkTemplateDadBattle.onClick = (_event) ->
|
||||
{
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Load song from template
|
||||
state.loadSongAsTemplate('dadbattle');
|
||||
}
|
||||
var linkTemplateBopeebo:Link = dialog.findComponent('splashTemplateBopeebo', Link);
|
||||
linkTemplateBopeebo.onClick = (_event) ->
|
||||
{
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Load song from template
|
||||
state.loadSongAsTemplate('bopeebo');
|
||||
}
|
||||
*/
|
||||
|
||||
var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox);
|
||||
|
||||
var songList:Array<String> = SongDataParser.listSongIds();
|
||||
|
||||
for (targetSongId in songList)
|
||||
{
|
||||
var songData = SongDataParser.fetchSong(targetSongId);
|
||||
var songData:Song = SongDataParser.fetchSong(targetSongId);
|
||||
|
||||
if (songData == null) continue;
|
||||
|
||||
var songName = songData.getDifficulty().songName;
|
||||
var songName:String = songData.getDifficulty().songName;
|
||||
|
||||
var linkTemplateSong:Link = new Link();
|
||||
linkTemplateSong.text = songName;
|
||||
linkTemplateSong.onClick = (_event) -> {
|
||||
linkTemplateSong.onClick = function(_event) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Load song from template
|
||||
|
@ -130,42 +130,99 @@ class ChartEditorDialogHandler
|
|||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog where the user uploads an instrumental for the current song.
|
||||
* @param state The current chart editor state.
|
||||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static function openUploadInstDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
|
||||
{
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT, true, closable);
|
||||
|
||||
var buttonCancel:Button = dialog.findComponent('dialogCancel', Button);
|
||||
|
||||
buttonCancel.onClick = function(_event) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
var instrumentalBox:Box = dialog.findComponent('instrumentalBox', Box);
|
||||
|
||||
instrumentalBox.onMouseOver = (_event) -> {
|
||||
instrumentalBox.onMouseOver = function(_event) {
|
||||
instrumentalBox.swapClass('upload-bg', 'upload-bg-hover');
|
||||
Cursor.cursorMode = Pointer;
|
||||
}
|
||||
|
||||
instrumentalBox.onMouseOut = (_event) -> {
|
||||
instrumentalBox.onMouseOut = function(_event) {
|
||||
instrumentalBox.swapClass('upload-bg-hover', 'upload-bg');
|
||||
Cursor.cursorMode = Default;
|
||||
}
|
||||
|
||||
var onDropFile:String->Void;
|
||||
|
||||
instrumentalBox.onClick = (_event) -> {
|
||||
Dialogs.openBinaryFile("Open Instrumental", [
|
||||
{label: "Audio File (.ogg)", extension: "ogg"}], function(selectedFile) {
|
||||
instrumentalBox.onClick = function(_event) {
|
||||
Dialogs.openBinaryFile('Open Instrumental', [
|
||||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
|
||||
if (selectedFile != null)
|
||||
{
|
||||
trace('Selected file: ' + selectedFile);
|
||||
state.loadInstrumentalFromBytes(selectedFile.bytes);
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
if (state.loadInstrumentalFromBytes(selectedFile.bytes))
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.fullPath);
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded instrumental track (${selectedFile.name})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to load instrumental (${selectedFile.fullPath})');
|
||||
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load instrumental track (${selectedFile.name})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDropFile = (path:String) -> {
|
||||
trace('Dropped file: ' + path);
|
||||
state.loadInstrumentalFromPath(path);
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
onDropFile = function(pathStr:String) {
|
||||
var path:Path = new Path(pathStr);
|
||||
trace('Dropped file (${path})');
|
||||
if (state.loadInstrumentalFromPath(path))
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded instrumental track (${path.file}.${path.ext})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load instrumental track (${path.file}.${path.ext})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
addDropHandler(instrumentalBox, onDropFile);
|
||||
|
@ -213,19 +270,14 @@ class ChartEditorDialogHandler
|
|||
{
|
||||
// a VERY short timer to wait for the mouse position to update
|
||||
new FlxTimer().start(0.01, function(_) {
|
||||
trace("mouseX: " + FlxG.mouse.screenX + ", mouseY: " + FlxG.mouse.screenY);
|
||||
|
||||
for (handler in dropHandlers)
|
||||
{
|
||||
if (handler.component.hitTest(FlxG.mouse.screenX, FlxG.mouse.screenY))
|
||||
{
|
||||
trace('File dropped on component! ' + handler.component.id);
|
||||
handler.handler(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
trace('File dropped on nothing!' + path);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -238,6 +290,12 @@ class ChartEditorDialogHandler
|
|||
{
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
|
||||
|
||||
var buttonCancel:Button = dialog.findComponent('dialogCancel', Button);
|
||||
|
||||
buttonCancel.onClick = function(_event) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
var dialogSongName:TextField = dialog.findComponent('dialogSongName', TextField);
|
||||
dialogSongName.onChange = function(event:UIEvent) {
|
||||
var valid:Bool = event.target.text != null && event.target.text != '';
|
||||
|
@ -272,25 +330,23 @@ class ChartEditorDialogHandler
|
|||
|
||||
var dialogStage:DropDown = dialog.findComponent('dialogStage', DropDown);
|
||||
dialogStage.onChange = function(event:UIEvent) {
|
||||
var valid = event.data != null && event.data.id != null;
|
||||
|
||||
if (event.data.id == null) return;
|
||||
if (event.data == null && event.data.id == null) return;
|
||||
state.currentSongMetadata.playData.stage = event.data.id;
|
||||
};
|
||||
state.currentSongMetadata.playData.stage = null;
|
||||
|
||||
var dialogNoteSkin:DropDown = dialog.findComponent('dialogNoteSkin', DropDown);
|
||||
dialogNoteSkin.onChange = (event:UIEvent) -> {
|
||||
dialogNoteSkin.onChange = function(event:UIEvent) {
|
||||
if (event.data.id == null) return;
|
||||
state.currentSongMetadata.playData.noteSkin = event.data.id;
|
||||
};
|
||||
state.currentSongMetadata.playData.noteSkin = null;
|
||||
|
||||
var dialogBPM:NumberStepper = dialog.findComponent('dialogBPM', NumberStepper);
|
||||
dialogBPM.onChange = (event:UIEvent) -> {
|
||||
dialogBPM.onChange = function(event:UIEvent) {
|
||||
if (event.value == null || event.value <= 0) return;
|
||||
|
||||
var timeChanges = state.currentSongMetadata.timeChanges;
|
||||
var timeChanges:Array<SongTimeChange> = state.currentSongMetadata.timeChanges;
|
||||
if (timeChanges == null || timeChanges.length == 0)
|
||||
{
|
||||
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])];
|
||||
|
@ -307,11 +363,9 @@ class ChartEditorDialogHandler
|
|||
|
||||
var dialogCharGrid:PropertyGrid = dialog.findComponent('dialogCharGrid', PropertyGrid);
|
||||
var dialogCharAdd:Button = dialog.findComponent('dialogCharAdd', Button);
|
||||
dialogCharAdd.onClick = (_event) -> {
|
||||
dialogCharAdd.onClick = function(event:UIEvent) {
|
||||
var charGroup:PropertyGroup;
|
||||
charGroup = buildCharGroup(state, null, () -> {
|
||||
dialogCharGrid.removeComponent(charGroup);
|
||||
});
|
||||
charGroup = buildCharGroup(state, null, () -> dialogCharGrid.removeComponent(charGroup));
|
||||
dialogCharGrid.addComponent(charGroup);
|
||||
};
|
||||
|
||||
|
@ -321,18 +375,16 @@ class ChartEditorDialogHandler
|
|||
dialogCharGrid.addComponent(buildCharGroup(state, 'bf', null));
|
||||
|
||||
var dialogContinue:Button = dialog.findComponent('dialogContinue', Button);
|
||||
dialogContinue.onClick = (_event) -> {
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
dialogContinue.onClick = (_event) -> dialog.hideDialog(DialogButton.APPLY);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
static function buildCharGroup(state:ChartEditorState, ?key:String = null, removeFunc:Void->Void):PropertyGroup
|
||||
static function buildCharGroup(state:ChartEditorState, key:String = null, removeFunc:Void->Void):PropertyGroup
|
||||
{
|
||||
var groupKey = key;
|
||||
var groupKey:String = key;
|
||||
|
||||
var getCharData = () -> {
|
||||
var getCharData:Void->SongPlayableChar = function() {
|
||||
if (groupKey == null) groupKey = 'newChar${state.currentSongMetadata.playData.playableChars.keys().count()}';
|
||||
|
||||
var result = state.currentSongMetadata.playData.playableChars.get(groupKey);
|
||||
|
@ -344,24 +396,24 @@ class ChartEditorDialogHandler
|
|||
return result;
|
||||
}
|
||||
|
||||
var moveCharGroup = (target:String) -> {
|
||||
var moveCharGroup:String->Void = function(target:String) {
|
||||
var charData = getCharData();
|
||||
state.currentSongMetadata.playData.playableChars.remove(groupKey);
|
||||
state.currentSongMetadata.playData.playableChars.set(target, charData);
|
||||
groupKey = target;
|
||||
}
|
||||
|
||||
var removeGroup = () -> {
|
||||
var removeGroup:Void->Void = function() {
|
||||
state.currentSongMetadata.playData.playableChars.remove(groupKey);
|
||||
removeFunc();
|
||||
}
|
||||
|
||||
var charData = getCharData();
|
||||
var charData:SongPlayableChar = getCharData();
|
||||
|
||||
var charGroup:PropertyGroup = cast state.buildComponent(CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT);
|
||||
|
||||
var charGroupPlayer:DropDown = charGroup.findComponent('charGroupPlayer', DropDown);
|
||||
charGroupPlayer.onChange = (event:UIEvent) -> {
|
||||
charGroupPlayer.onChange = function(event:UIEvent) {
|
||||
charGroup.text = event.data.text;
|
||||
moveCharGroup(event.data.id);
|
||||
};
|
||||
|
@ -373,19 +425,19 @@ class ChartEditorDialogHandler
|
|||
}
|
||||
|
||||
var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown);
|
||||
charGroupOpponent.onChange = (event:UIEvent) -> {
|
||||
charGroupOpponent.onChange = function(event:UIEvent) {
|
||||
charData.opponent = event.data.id;
|
||||
};
|
||||
charGroupOpponent.value = getCharData().opponent;
|
||||
|
||||
var charGroupGirlfriend:DropDown = charGroup.findComponent('charGroupGirlfriend', DropDown);
|
||||
charGroupGirlfriend.onChange = (event:UIEvent) -> {
|
||||
charGroupGirlfriend.onChange = function(event:UIEvent) {
|
||||
charData.girlfriend = event.data.id;
|
||||
};
|
||||
charGroupGirlfriend.value = getCharData().girlfriend;
|
||||
|
||||
var charGroupRemove:Button = charGroup.findComponent('charGroupRemove', Button);
|
||||
charGroupRemove.onClick = (_event:MouseEvent) -> {
|
||||
charGroupRemove.onClick = function(event:UIEvent) {
|
||||
removeGroup();
|
||||
};
|
||||
|
||||
|
@ -394,20 +446,31 @@ class ChartEditorDialogHandler
|
|||
return charGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog where the user uploads vocals for the current song.
|
||||
* @param state The current chart editor state.
|
||||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static function openUploadVocalsDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
|
||||
{
|
||||
var charIdsForVocals = [];
|
||||
var charIdsForVocals:Array<String> = [];
|
||||
|
||||
for (charKey in state.currentSongMetadata.playData.playableChars.keys())
|
||||
{
|
||||
var charData = state.currentSongMetadata.playData.playableChars.get(charKey);
|
||||
var charData:SongPlayableChar = state.currentSongMetadata.playData.playableChars.get(charKey);
|
||||
charIdsForVocals.push(charKey);
|
||||
if (charData.opponent != null) charIdsForVocals.push(charData.opponent);
|
||||
}
|
||||
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT, true, closable);
|
||||
|
||||
var dialogContainer = dialog.findComponent('vocalContainer');
|
||||
var dialogContainer:Component = dialog.findComponent('vocalContainer');
|
||||
|
||||
var buttonCancel:Button = dialog.findComponent('dialogCancel', Button);
|
||||
buttonCancel.onClick = function(_event) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
var dialogNoVocals:Button = dialog.findComponent('dialogNoVocals', Button);
|
||||
dialogNoVocals.onClick = function(_event) {
|
||||
|
@ -421,20 +484,42 @@ class ChartEditorDialogHandler
|
|||
var charMetadata:BaseCharacter = CharacterDataParser.fetchCharacter(charKey);
|
||||
var charName:String = charMetadata.characterName;
|
||||
|
||||
var vocalsEntry = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
|
||||
var vocalsEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
|
||||
|
||||
var vocalsEntryLabel:Label = vocalsEntry.findComponent('vocalsEntryLabel', Label);
|
||||
vocalsEntryLabel.text = 'Click to browse for a vocal track for $charName.';
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
|
||||
var onDropFile:String->Void = function(fullPath:String) {
|
||||
trace('Selected file: $fullPath');
|
||||
var directory:String = Path.directory(fullPath);
|
||||
var filename:String = Path.withoutDirectory(directory);
|
||||
var onDropFile:String->Void = function(pathStr:String) {
|
||||
trace('Selected file: $pathStr');
|
||||
var path:Path = new Path(pathStr);
|
||||
|
||||
vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${filename}';
|
||||
state.loadVocalsFromPath(fullPath, charKey);
|
||||
dialogNoVocals.hidden = true;
|
||||
removeDropHandler(onDropFile);
|
||||
if (state.loadVocalsFromPath(path, charKey))
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded vocal track for $charName (${path.file}.${path.ext})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
vocalsEntryLabel.text = 'Vocals for $charName (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||
dialogNoVocals.hidden = true;
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vocals failed to load.
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load vocal track (${path.file}.${path.ext})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
}
|
||||
};
|
||||
|
||||
vocalsEntry.onClick = function(_event) {
|
||||
|
@ -449,11 +534,10 @@ class ChartEditorDialogHandler
|
|||
removeDropHandler(onDropFile);
|
||||
}
|
||||
});
|
||||
|
||||
// onDropFile
|
||||
addDropHandler(vocalsEntry, onDropFile);
|
||||
}
|
||||
|
||||
// onDropFile
|
||||
addDropHandler(vocalsEntry, onDropFile);
|
||||
dialogContainer.addComponent(vocalsEntry);
|
||||
}
|
||||
|
||||
|
@ -463,14 +547,14 @@ class ChartEditorDialogHandler
|
|||
dialog.hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
|
||||
// TODO: Redo the logic for file drop handler to be more robust.
|
||||
// We need to distinguish which component the mouse is over when the file is dropped.
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog displaying the user guide, providing guidance and help on how to use the chart editor.
|
||||
*
|
||||
* @param state The current chart editor state.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static inline function openUserGuideDialog(state:ChartEditorState):Dialog
|
||||
{
|
||||
|
@ -490,7 +574,7 @@ class ChartEditorDialogHandler
|
|||
dialog.showDialog(modal);
|
||||
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
dialog.onDialogClosed = (_event) -> {
|
||||
dialog.onDialogClosed = function(event:UIEvent) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import funkin.ui.debug.charting.ChartEditorCommand;
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import funkin.input.TurboKeyHandler;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import haxe.DynamicAccess;
|
||||
|
@ -18,7 +21,6 @@ import funkin.audio.visualize.PolygonSpectogram;
|
|||
import funkin.audio.VocalGroup;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
import funkin.modding.events.ScriptEventDispatcher;
|
||||
import funkin.play.HealthIcon;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.song.SongData.SongChartData;
|
||||
|
@ -27,8 +29,6 @@ import funkin.play.song.SongData.SongEventData;
|
|||
import funkin.play.song.SongData.SongMetadata;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import funkin.play.song.SongDataUtils;
|
||||
import funkin.play.song.SongSerializer;
|
||||
import funkin.ui.debug.charting.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme;
|
||||
import funkin.ui.debug.charting.ChartEditorToolboxHandler.ChartEditorToolMode;
|
||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||
|
@ -37,23 +37,16 @@ import funkin.util.Constants;
|
|||
import funkin.util.FileUtil;
|
||||
import funkin.util.DateUtil;
|
||||
import funkin.util.SerializerUtil;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.CheckBox;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.Slider;
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
import haxe.ui.containers.dialogs.MessageBox;
|
||||
import haxe.ui.containers.menus.MenuCheckBox;
|
||||
import haxe.ui.containers.menus.MenuItem;
|
||||
import haxe.ui.containers.SideBar;
|
||||
import haxe.ui.containers.TreeView;
|
||||
import haxe.ui.containers.TreeViewNode;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.DragEvent;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import lime.media.AudioBuffer;
|
||||
import funkin.util.WindowUtil;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.geom.Rectangle;
|
||||
|
@ -523,14 +516,34 @@ class ChartEditorState extends HaxeUIState
|
|||
var redoHistory:Array<ChartEditorCommand> = [];
|
||||
|
||||
/**
|
||||
* Variable used to track how long the user has been holding the undo keybind.
|
||||
* Handler used to track how long the user has been holding the undo keybind.
|
||||
*/
|
||||
var undoHeldTime:Float = 0.0;
|
||||
var undoKeyHandler:TurboKeyHandler = TurboKeyHandler.build([FlxKey.CONTROL, FlxKey.Z]);
|
||||
|
||||
/**
|
||||
* Variable used to track how long the user has been holding the redo keybind.
|
||||
*/
|
||||
var redoHeldTime:Float = 0.0;
|
||||
var redoKeyHandler:TurboKeyHandler = TurboKeyHandler.build([FlxKey.CONTROL, FlxKey.Y]);
|
||||
|
||||
/**
|
||||
* Variable used to track how long the user has been holding the up keybind.
|
||||
*/
|
||||
var upKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.UP);
|
||||
|
||||
/**
|
||||
* Variable used to track how long the user has been holding the down keybind.
|
||||
*/
|
||||
var downKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.DOWN);
|
||||
|
||||
/**
|
||||
* Variable used to track how long the user has been holding the page-up keybind.
|
||||
*/
|
||||
var pageUpKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.PAGEUP);
|
||||
|
||||
/**
|
||||
* Variable used to track how long the user has been holding the page-down keybind.
|
||||
*/
|
||||
var pageDownKeyHandler:TurboKeyHandler = TurboKeyHandler.build(FlxKey.PAGEDOWN);
|
||||
|
||||
/**
|
||||
* Whether the undo/redo histories have changed since the last time the UI was updated.
|
||||
|
@ -728,8 +741,7 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
function set_currentSongChartEventData(value:Array<SongEventData>):Array<SongEventData>
|
||||
{
|
||||
currentSongChartData.events = value;
|
||||
return value;
|
||||
return currentSongChartData.events = value;
|
||||
}
|
||||
|
||||
public var currentSongNoteSkin(get, set):String;
|
||||
|
@ -911,7 +923,7 @@ class ChartEditorState extends HaxeUIState
|
|||
super(CHART_EDITOR_LAYOUT);
|
||||
}
|
||||
|
||||
override function create()
|
||||
override function create():Void
|
||||
{
|
||||
// Get rid of any music from the previous state.
|
||||
FlxG.sound.music.stop();
|
||||
|
@ -931,18 +943,14 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
// Setup the onClick listeners for the UI after it's been created.
|
||||
setupUIListeners();
|
||||
setupTurboKeyHandlers();
|
||||
|
||||
setupAutoSave();
|
||||
|
||||
// TODO: We should be loading the music later when the user requests it.
|
||||
// loadDefaultMusic();
|
||||
|
||||
// TODO: Change to false.
|
||||
var canCloseInitialDialog = true;
|
||||
ChartEditorDialogHandler.openWelcomeDialog(this, canCloseInitialDialog);
|
||||
ChartEditorDialogHandler.openWelcomeDialog(this, false);
|
||||
}
|
||||
|
||||
function buildDefaultSongData()
|
||||
function buildDefaultSongData():Void
|
||||
{
|
||||
selectedVariation = Constants.DEFAULT_VARIATION;
|
||||
selectedDifficulty = Constants.DEFAULT_DIFFICULTY;
|
||||
|
@ -959,7 +967,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Builds and displays the background sprite.
|
||||
*/
|
||||
function buildBackground()
|
||||
function buildBackground():Void
|
||||
{
|
||||
menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat'));
|
||||
add(menuBG);
|
||||
|
@ -973,7 +981,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Builds and displays the chart editor grid, including the playhead and cursor.
|
||||
*/
|
||||
function buildGrid()
|
||||
function buildGrid():Void
|
||||
{
|
||||
gridTiledSprite = new FlxTiledSprite(gridBitmap, gridBitmap.width, 1000, false, true);
|
||||
gridTiledSprite.x = FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; // Center the grid.
|
||||
|
@ -1032,7 +1040,7 @@ class ChartEditorState extends HaxeUIState
|
|||
add(healthIconBF);
|
||||
}
|
||||
|
||||
function buildSelectionBox()
|
||||
function buildSelectionBox():Void
|
||||
{
|
||||
selectionBoxSprite.scrollFactor.set(0, 0);
|
||||
add(selectionBoxSprite);
|
||||
|
@ -1040,7 +1048,7 @@ class ChartEditorState extends HaxeUIState
|
|||
setSelectionBoxBounds();
|
||||
}
|
||||
|
||||
function setSelectionBoxBounds(?bounds:FlxRect = null)
|
||||
function setSelectionBoxBounds(?bounds:FlxRect = null):Void
|
||||
{
|
||||
if (bounds == null)
|
||||
{
|
||||
|
@ -1058,7 +1066,7 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
}
|
||||
|
||||
function buildSpectrogram(target:FlxSound)
|
||||
function buildSpectrogram(target:FlxSound):Void
|
||||
{
|
||||
gridSpectrogram = new PolygonSpectogram(target, SPECTROGRAM_COLOR, FlxG.height / 2, Math.floor(FlxG.height / 2));
|
||||
// Halfway through the grid.
|
||||
|
@ -1075,7 +1083,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Builds the group that will hold all the notes.
|
||||
*/
|
||||
function buildNoteGroup()
|
||||
function buildNoteGroup():Void
|
||||
{
|
||||
renderedNotes = new FlxTypedSpriteGroup<ChartEditorNoteSprite>();
|
||||
renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y);
|
||||
|
@ -1148,23 +1156,23 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
// Add functionality to the playbar.
|
||||
|
||||
addUIClickListener('playbarPlay', (event:MouseEvent) -> toggleAudioPlayback());
|
||||
addUIClickListener('playbarStart', (event:MouseEvent) -> playbarButtonPressed = 'playbarStart');
|
||||
addUIClickListener('playbarBack', (event:MouseEvent) -> playbarButtonPressed = 'playbarBack');
|
||||
addUIClickListener('playbarForward', (event:MouseEvent) -> playbarButtonPressed = 'playbarForward');
|
||||
addUIClickListener('playbarEnd', (event:MouseEvent) -> playbarButtonPressed = 'playbarEnd');
|
||||
addUIClickListener('playbarPlay', _ -> toggleAudioPlayback());
|
||||
addUIClickListener('playbarStart', _ -> playbarButtonPressed = 'playbarStart');
|
||||
addUIClickListener('playbarBack', _ -> playbarButtonPressed = 'playbarBack');
|
||||
addUIClickListener('playbarForward', _ -> playbarButtonPressed = 'playbarForward');
|
||||
addUIClickListener('playbarEnd', _ -> playbarButtonPressed = 'playbarEnd');
|
||||
|
||||
// Add functionality to the menu items.
|
||||
|
||||
addUIClickListener('menubarItemNewChart', (event:MouseEvent) -> ChartEditorDialogHandler.openWelcomeDialog(this, true));
|
||||
addUIClickListener('menubarItemSaveChartAs', (event:MouseEvent) -> exportAllSongData());
|
||||
addUIClickListener('menubarItemLoadInst', (event:MouseEvent) -> ChartEditorDialogHandler.openUploadInstDialog(this, true));
|
||||
addUIClickListener('menubarItemNewChart', _ -> ChartEditorDialogHandler.openWelcomeDialog(this, true));
|
||||
addUIClickListener('menubarItemSaveChartAs', _ -> exportAllSongData());
|
||||
addUIClickListener('menubarItemLoadInst', _ -> ChartEditorDialogHandler.openUploadInstDialog(this, true));
|
||||
|
||||
addUIClickListener('menubarItemUndo', (event:MouseEvent) -> undoLastCommand());
|
||||
addUIClickListener('menubarItemUndo', _ -> undoLastCommand());
|
||||
|
||||
addUIClickListener('menubarItemRedo', (event:MouseEvent) -> redoLastCommand());
|
||||
addUIClickListener('menubarItemRedo', _ -> redoLastCommand());
|
||||
|
||||
addUIClickListener('menubarItemCopy', (event:MouseEvent) -> {
|
||||
addUIClickListener('menubarItemCopy', function(_) {
|
||||
// Doesn't use a command because it's not undoable.
|
||||
SongDataUtils.writeItemsToClipboard(
|
||||
{
|
||||
|
@ -1173,15 +1181,11 @@ class ChartEditorState extends HaxeUIState
|
|||
});
|
||||
});
|
||||
|
||||
addUIClickListener('menubarItemCut', (event:MouseEvent) -> {
|
||||
performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
});
|
||||
addUIClickListener('menubarItemCut', _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
|
||||
addUIClickListener('menubarItemPaste', (event:MouseEvent) -> {
|
||||
performCommand(new PasteItemsCommand(scrollPositionInMs + playheadPositionInMs));
|
||||
});
|
||||
addUIClickListener('menubarItemPaste', _ -> performCommand(new PasteItemsCommand(scrollPositionInMs + playheadPositionInMs)));
|
||||
|
||||
addUIClickListener('menubarItemDelete', (event:MouseEvent) -> {
|
||||
addUIClickListener('menubarItemDelete', function(_) {
|
||||
if (currentNoteSelection.length > 0 && currentEventSelection.length > 0)
|
||||
{
|
||||
performCommand(new RemoveItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
|
@ -1200,84 +1204,60 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
});
|
||||
|
||||
addUIClickListener('menubarItemSelectAll', (event:MouseEvent) -> {
|
||||
performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
});
|
||||
addUIClickListener('menubarItemSelectAll', _ -> performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
|
||||
addUIClickListener('menubarItemSelectInverse', (event:MouseEvent) -> {
|
||||
performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
});
|
||||
addUIClickListener('menubarItemSelectInverse', _ -> performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
|
||||
addUIClickListener('menubarItemSelectNone', (event:MouseEvent) -> {
|
||||
performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
});
|
||||
addUIClickListener('menubarItemSelectNone', _ -> performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
|
||||
addUIClickListener('menubarItemSelectRegion', (event:MouseEvent) ->
|
||||
{
|
||||
// TODO: Implement this.
|
||||
});
|
||||
// TODO: Implement these.
|
||||
// addUIClickListener('menubarItemSelectRegion', _ -> doSomething());
|
||||
// addUIClickListener('menubarItemSelectBeforeCursor', _ -> doSomething());
|
||||
// addUIClickListener('menubarItemSelectAfterCursor', _ -> doSomething());
|
||||
|
||||
addUIClickListener('menubarItemSelectBeforeCursor', (event:MouseEvent) ->
|
||||
{
|
||||
// TODO: Implement this.
|
||||
});
|
||||
addUIClickListener('menubarItemAbout', _ -> ChartEditorDialogHandler.openAboutDialog(this));
|
||||
|
||||
addUIClickListener('menubarItemSelectAfterCursor', (event:MouseEvent) ->
|
||||
{
|
||||
// TODO: Implement this.
|
||||
});
|
||||
addUIClickListener('menubarItemUserGuide', _ -> ChartEditorDialogHandler.openUserGuideDialog(this));
|
||||
|
||||
addUIClickListener('menubarItemAbout', (event:MouseEvent) -> ChartEditorDialogHandler.openAboutDialog(this));
|
||||
|
||||
addUIClickListener('menubarItemUserGuide', (event:MouseEvent) -> ChartEditorDialogHandler.openUserGuideDialog(this));
|
||||
|
||||
addUIChangeListener('menubarItemDownscroll', (event:UIEvent) -> {
|
||||
isViewDownscroll = event.value;
|
||||
});
|
||||
addUIChangeListener('menubarItemDownscroll', event -> isViewDownscroll = event.value);
|
||||
setUICheckboxSelected('menubarItemDownscroll', isViewDownscroll);
|
||||
|
||||
addUIChangeListener('menuBarItemThemeLight', (event:UIEvent) -> {
|
||||
addUIChangeListener('menuBarItemThemeLight', function(event:UIEvent) {
|
||||
if (event.target.value) currentTheme = ChartEditorTheme.Light;
|
||||
});
|
||||
setUICheckboxSelected('menuBarItemThemeLight', currentTheme == ChartEditorTheme.Light);
|
||||
|
||||
addUIChangeListener('menuBarItemThemeDark', (event:UIEvent) -> {
|
||||
addUIChangeListener('menuBarItemThemeDark', function(event:UIEvent) {
|
||||
if (event.target.value) currentTheme = ChartEditorTheme.Dark;
|
||||
});
|
||||
setUICheckboxSelected('menuBarItemThemeDark', currentTheme == ChartEditorTheme.Dark);
|
||||
|
||||
addUIChangeListener('menubarItemMetronomeEnabled', (event:UIEvent) -> {
|
||||
shouldPlayMetronome = event.value;
|
||||
});
|
||||
addUIChangeListener('menubarItemMetronomeEnabled', event -> shouldPlayMetronome = event.value);
|
||||
setUICheckboxSelected('menubarItemMetronomeEnabled', shouldPlayMetronome);
|
||||
|
||||
addUIChangeListener('menubarItemPlayerHitsounds', (event:UIEvent) -> {
|
||||
hitsoundsEnabledPlayer = event.value;
|
||||
});
|
||||
addUIChangeListener('menubarItemPlayerHitsounds', event -> hitsoundsEnabledPlayer = event.value);
|
||||
setUICheckboxSelected('menubarItemPlayerHitsounds', hitsoundsEnabledPlayer);
|
||||
|
||||
addUIChangeListener('menubarItemOpponentHitsounds', (event:UIEvent) -> {
|
||||
hitsoundsEnabledOpponent = event.value;
|
||||
});
|
||||
addUIChangeListener('menubarItemOpponentHitsounds', event -> hitsoundsEnabledOpponent = event.value);
|
||||
setUICheckboxSelected('menubarItemOpponentHitsounds', hitsoundsEnabledOpponent);
|
||||
|
||||
var instVolumeLabel:Label = findComponent('menubarLabelVolumeInstrumental', Label);
|
||||
addUIChangeListener('menubarItemVolumeInstrumental', (event:UIEvent) -> {
|
||||
addUIChangeListener('menubarItemVolumeInstrumental', function(event:UIEvent) {
|
||||
var volume:Float = event.value / 100.0;
|
||||
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
||||
instVolumeLabel.text = 'Instrumental - ${Std.int(event.value)}%';
|
||||
});
|
||||
|
||||
var vocalsVolumeLabel:Label = findComponent('menubarLabelVolumeVocals', Label);
|
||||
addUIChangeListener('menubarItemVolumeVocals', (event:UIEvent) -> {
|
||||
addUIChangeListener('menubarItemVolumeVocals', function(event:UIEvent) {
|
||||
var volume:Float = event.value / 100.0;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume;
|
||||
vocalsVolumeLabel.text = 'Vocals - ${Std.int(event.value)}%';
|
||||
});
|
||||
|
||||
var playbackSpeedLabel:Label = findComponent('menubarLabelPlaybackSpeed', Label);
|
||||
addUIChangeListener('menubarItemPlaybackSpeed', (event:UIEvent) -> {
|
||||
var pitch = event.value * 2.0 / 100.0;
|
||||
addUIChangeListener('menubarItemPlaybackSpeed', function(event:UIEvent) {
|
||||
var pitch:Float = event.value * 2.0 / 100.0;
|
||||
#if FLX_PITCH
|
||||
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pitch = pitch;
|
||||
|
@ -1285,40 +1265,45 @@ class ChartEditorState extends HaxeUIState
|
|||
playbackSpeedLabel.text = 'Playback Speed - ${Std.int(pitch * 100) / 100}x';
|
||||
});
|
||||
|
||||
addUIChangeListener('menubarItemToggleToolboxTools', (event:UIEvent) -> {
|
||||
ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT, event.value);
|
||||
});
|
||||
// setUICheckboxSelected('menubarItemToggleToolboxTools', true);
|
||||
addUIChangeListener('menubarItemToggleToolboxNotes', (event:UIEvent) -> {
|
||||
ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value);
|
||||
});
|
||||
addUIChangeListener('menubarItemToggleToolboxEvents', (event:UIEvent) -> {
|
||||
ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT, event.value);
|
||||
});
|
||||
addUIChangeListener('menubarItemToggleToolboxDifficulty', (event:UIEvent) -> {
|
||||
ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value);
|
||||
});
|
||||
addUIChangeListener('menubarItemToggleToolboxMetadata', (event:UIEvent) -> {
|
||||
ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value);
|
||||
});
|
||||
addUIChangeListener('menubarItemToggleToolboxCharacters', (event:UIEvent) -> {
|
||||
ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT, event.value);
|
||||
});
|
||||
addUIChangeListener('menubarItemToggleToolboxPlayerPreview', (event:UIEvent) -> {
|
||||
ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT, event.value);
|
||||
});
|
||||
addUIChangeListener('menubarItemToggleToolboxOpponentPreview', (event:UIEvent) -> {
|
||||
ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT, event.value);
|
||||
});
|
||||
addUIChangeListener('menubarItemToggleToolboxTools',
|
||||
event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxNotes',
|
||||
event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxEvents',
|
||||
event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxDifficulty',
|
||||
event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxMetadata',
|
||||
event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxCharacters',
|
||||
event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxPlayerPreview',
|
||||
event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxOpponentPreview',
|
||||
event -> ChartEditorToolboxHandler.setToolboxState(this, CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT, event.value));
|
||||
|
||||
// TODO: Pass specific HaxeUI components to add context menus to them.
|
||||
registerContextMenu(null, Paths.ui('chart-editor/context/test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize TurboKeyHandlers and add them to the state (so `update()` is called)
|
||||
* We can then probe `keyHandler.activated` to see if the key combo's action should be taken.
|
||||
*/
|
||||
function setupTurboKeyHandlers():Void
|
||||
{
|
||||
add(undoKeyHandler);
|
||||
add(redoKeyHandler);
|
||||
add(upKeyHandler);
|
||||
add(downKeyHandler);
|
||||
add(pageUpKeyHandler);
|
||||
add(pageDownKeyHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup timers and listerners to handle auto-save.
|
||||
*/
|
||||
function setupAutoSave()
|
||||
function setupAutoSave():Void
|
||||
{
|
||||
WindowUtil.windowExit.add(onWindowClose);
|
||||
saveDataDirty = false;
|
||||
|
@ -1327,7 +1312,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Called after 5 minutes without saving.
|
||||
*/
|
||||
function autoSave()
|
||||
function autoSave():Void
|
||||
{
|
||||
saveDataDirty = false;
|
||||
|
||||
|
@ -1466,42 +1451,49 @@ class ChartEditorState extends HaxeUIState
|
|||
var shouldPause:Bool = false;
|
||||
|
||||
// Up Arrow = Scroll Up
|
||||
if (FlxG.keys.justPressed.UP)
|
||||
if (upKeyHandler.activated)
|
||||
{
|
||||
scrollAmount = -GRID_SIZE * 0.25 * 5;
|
||||
scrollAmount = -GRID_SIZE * 0.25 * 5.0;
|
||||
shouldPause = true;
|
||||
}
|
||||
// Down Arrow = Scroll Down
|
||||
if (FlxG.keys.justPressed.DOWN)
|
||||
if (downKeyHandler.activated)
|
||||
{
|
||||
scrollAmount = GRID_SIZE * 0.25 * 5;
|
||||
scrollAmount = GRID_SIZE * 0.25 * 5.0;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
// PAGE UP = Jump Up 1 Measure
|
||||
if (FlxG.keys.justPressed.PAGEUP)
|
||||
if (pageUpKeyHandler.activated)
|
||||
{
|
||||
scrollAmount = -GRID_SIZE * 4 * Conductor.beatsPerMeasure;
|
||||
shouldPause = true;
|
||||
}
|
||||
if (playbarButtonPressed == 'playbarBack')
|
||||
{
|
||||
playbarButtonPressed = '';
|
||||
scrollAmount = -GRID_SIZE * 4 * Conductor.beatsPerMeasure;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
// PAGE DOWN = Jump Down 1 Measure
|
||||
if (FlxG.keys.justPressed.PAGEDOWN)
|
||||
if (pageDownKeyHandler.activated)
|
||||
{
|
||||
scrollAmount = GRID_SIZE * 4 * Conductor.beatsPerMeasure;
|
||||
shouldPause = true;
|
||||
}
|
||||
if (playbarButtonPressed == 'playbarForward')
|
||||
{
|
||||
playbarButtonPressed = '';
|
||||
scrollAmount = GRID_SIZE * 4 * Conductor.beatsPerMeasure;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
// Mouse Wheel = Scroll
|
||||
if (FlxG.mouse.wheel != 0 && !FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
scrollAmount = -10 * FlxG.mouse.wheel;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
// Middle Mouse + Drag = Scroll but move the playhead the same amount.
|
||||
|
@ -1532,6 +1524,7 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
playheadAmount = scrollAmount;
|
||||
scrollAmount = 0;
|
||||
shouldPause = false;
|
||||
}
|
||||
|
||||
// HOME = Scroll to Top
|
||||
|
@ -1540,12 +1533,14 @@ class ChartEditorState extends HaxeUIState
|
|||
// Scroll amount is the difference between the current position and the top.
|
||||
scrollAmount = 0 - this.scrollPositionInPixels;
|
||||
playheadAmount = 0 - this.playheadPositionInPixels;
|
||||
shouldPause = true;
|
||||
}
|
||||
if (playbarButtonPressed == 'playbarStart')
|
||||
{
|
||||
playbarButtonPressed = '';
|
||||
scrollAmount = 0 - this.scrollPositionInPixels;
|
||||
playheadAmount = 0 - this.playheadPositionInPixels;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
// END = Scroll to Bottom
|
||||
|
@ -1553,11 +1548,13 @@ class ChartEditorState extends HaxeUIState
|
|||
{
|
||||
// Scroll amount is the difference between the current position and the bottom.
|
||||
scrollAmount = this.songLengthInPixels - this.scrollPositionInPixels;
|
||||
shouldPause = true;
|
||||
}
|
||||
if (playbarButtonPressed == 'playbarEnd')
|
||||
{
|
||||
playbarButtonPressed = '';
|
||||
scrollAmount = this.songLengthInPixels - this.scrollPositionInPixels;
|
||||
shouldPause = true;
|
||||
}
|
||||
|
||||
// Apply the scroll amount.
|
||||
|
@ -1566,9 +1563,10 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
// Resync the conductor and audio tracks.
|
||||
if (scrollAmount != 0 || playheadAmount != 0) moveSongToScrollPosition();
|
||||
if (shouldPause) stopAudioPlayback();
|
||||
}
|
||||
|
||||
function handleZoom()
|
||||
function handleZoom():Void
|
||||
{
|
||||
if (FlxG.keys.justPressed.MINUS)
|
||||
{
|
||||
|
@ -1591,7 +1589,7 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
}
|
||||
|
||||
function handleSnap()
|
||||
function handleSnap():Void
|
||||
{
|
||||
if (FlxG.keys.justPressed.LEFT)
|
||||
{
|
||||
|
@ -1607,7 +1605,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Handle display of the mouse cursor.
|
||||
*/
|
||||
function handleCursor()
|
||||
function handleCursor():Void
|
||||
{
|
||||
// Note: If a menu is open in HaxeUI, don't handle cursor behavior.
|
||||
var shouldHandleCursor = !isCursorOverHaxeUI || (selectionBoxStartPos != null);
|
||||
|
@ -2330,7 +2328,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Handles display elements for the playbar at the bottom.
|
||||
*/
|
||||
function handlePlaybar()
|
||||
function handlePlaybar():Void
|
||||
{
|
||||
// Make sure the playbar is never nudged out of the correct spot.
|
||||
playbarHeadLayout.x = 4;
|
||||
|
@ -2362,7 +2360,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Handle keybinds for File menu items.
|
||||
*/
|
||||
function handleFileKeybinds()
|
||||
function handleFileKeybinds():Void
|
||||
{
|
||||
// CTRL + Q = Quit to Menu
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.Q)
|
||||
|
@ -2374,48 +2372,20 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Handle keybinds for edit menu items.
|
||||
*/
|
||||
function handleEditKeybinds()
|
||||
function handleEditKeybinds():Void
|
||||
{
|
||||
// CTRL + Z = Undo
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.Z)
|
||||
if (undoKeyHandler.activated)
|
||||
{
|
||||
undoLastCommand();
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.Z && !FlxG.keys.pressed.Y)
|
||||
{
|
||||
undoHeldTime += FlxG.elapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
undoHeldTime = 0;
|
||||
}
|
||||
if (undoHeldTime > RAPID_UNDO_DELAY + RAPID_UNDO_INTERVAL)
|
||||
{
|
||||
undoLastCommand();
|
||||
undoHeldTime -= RAPID_UNDO_INTERVAL;
|
||||
}
|
||||
|
||||
// CTRL + Y = Redo
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.Y)
|
||||
if (redoKeyHandler.activated)
|
||||
{
|
||||
redoLastCommand();
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.Y && !FlxG.keys.pressed.Z)
|
||||
{
|
||||
redoHeldTime += FlxG.elapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
redoHeldTime = 0;
|
||||
}
|
||||
if (redoHeldTime > RAPID_UNDO_DELAY + RAPID_UNDO_INTERVAL)
|
||||
{
|
||||
redoLastCommand();
|
||||
redoHeldTime -= RAPID_UNDO_INTERVAL;
|
||||
}
|
||||
|
||||
// CTRL + C = Copy
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.C)
|
||||
{
|
||||
|
@ -2485,25 +2455,25 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Handle keybinds for View menu items.
|
||||
*/
|
||||
function handleViewKeybinds() {}
|
||||
function handleViewKeybinds():Void {}
|
||||
|
||||
/**
|
||||
* Handle keybinds for Help menu items.
|
||||
*/
|
||||
function handleHelpKeybinds()
|
||||
function handleHelpKeybinds():Void
|
||||
{
|
||||
// F1 = Open Help
|
||||
if (FlxG.keys.justPressed.F1) ChartEditorDialogHandler.openUserGuideDialog(this);
|
||||
}
|
||||
|
||||
function handleToolboxes()
|
||||
function handleToolboxes():Void
|
||||
{
|
||||
handleDifficultyToolbox();
|
||||
handlePlayerPreviewToolbox();
|
||||
handleOpponentPreviewToolbox();
|
||||
}
|
||||
|
||||
function handleDifficultyToolbox()
|
||||
function handleDifficultyToolbox():Void
|
||||
{
|
||||
if (difficultySelectDirty)
|
||||
{
|
||||
|
@ -2552,7 +2522,7 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
}
|
||||
|
||||
function handlePlayerPreviewToolbox()
|
||||
function handlePlayerPreviewToolbox():Void
|
||||
{
|
||||
// Manage the Select Difficulty tree view.
|
||||
var charPreviewToolbox = ChartEditorToolboxHandler.getToolbox(this, CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT);
|
||||
|
@ -2564,7 +2534,7 @@ class ChartEditorState extends HaxeUIState
|
|||
currentPlayerCharacterPlayer = charPlayer;
|
||||
}
|
||||
|
||||
function handleOpponentPreviewToolbox()
|
||||
function handleOpponentPreviewToolbox():Void
|
||||
{
|
||||
// Manage the Select Difficulty tree view.
|
||||
var charPreviewToolbox = ChartEditorToolboxHandler.getToolbox(this, CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT);
|
||||
|
@ -2576,7 +2546,7 @@ class ChartEditorState extends HaxeUIState
|
|||
currentOpponentCharacterPlayer = charPlayer;
|
||||
}
|
||||
|
||||
override function dispatchEvent(event:ScriptEvent)
|
||||
override function dispatchEvent(event:ScriptEvent):Void
|
||||
{
|
||||
super.dispatchEvent(event);
|
||||
|
||||
|
@ -2660,9 +2630,9 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
}
|
||||
|
||||
function addDifficulty(variation:String) {}
|
||||
function addDifficulty(variation:String):Void {}
|
||||
|
||||
function addVariation(variationId:String)
|
||||
function addVariation(variationId:String):Void
|
||||
{
|
||||
// Create a new variation with the specified ID.
|
||||
songMetadata.set(variationId, currentSongMetadata.clone(variationId));
|
||||
|
@ -2673,7 +2643,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Handle the player preview/gameplay test area on the left side.
|
||||
*/
|
||||
function handlePlayerDisplay() {}
|
||||
function handlePlayerDisplay():Void {}
|
||||
|
||||
/**
|
||||
* Handles the note preview/scroll area on the right side.
|
||||
|
@ -2683,7 +2653,7 @@ class ChartEditorState extends HaxeUIState
|
|||
* - Scrolling the note preview area down if the note preview is taller than the screen,
|
||||
* and the viewport nears the end of the visible area.
|
||||
*/
|
||||
function handleNotePreview()
|
||||
function handleNotePreview():Void
|
||||
{
|
||||
//
|
||||
if (notePreviewDirty)
|
||||
|
@ -2703,13 +2673,13 @@ class ChartEditorState extends HaxeUIState
|
|||
* Perform a spot update on the note preview, by editing the note preview
|
||||
* only where necessary. More efficient than a full update.
|
||||
*/
|
||||
function updateNotePreview(note:SongNoteData, ?deleteNote:Bool = false) {}
|
||||
function updateNotePreview(note:SongNoteData, ?deleteNote:Bool = false):Void {}
|
||||
|
||||
/**
|
||||
* Handles passive behavior of the menu bar, such as updating labels or enabled/disabled status.
|
||||
* Does not handle onClick ACTIONS of the menubar.
|
||||
*/
|
||||
function handleMenubar()
|
||||
function handleMenubar():Void
|
||||
{
|
||||
if (commandHistoryDirty)
|
||||
{
|
||||
|
@ -2765,7 +2735,7 @@ class ChartEditorState extends HaxeUIState
|
|||
/**
|
||||
* Handle syncronizing the conductor with the music playback.
|
||||
*/
|
||||
function handleMusicPlayback()
|
||||
function handleMusicPlayback():Void
|
||||
{
|
||||
if (audioInstTrack != null && audioInstTrack.playing)
|
||||
{
|
||||
|
@ -2856,21 +2826,25 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
}
|
||||
|
||||
function startAudioPlayback()
|
||||
function startAudioPlayback():Void
|
||||
{
|
||||
if (audioInstTrack != null) audioInstTrack.play();
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.play();
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.play();
|
||||
|
||||
setComponentText('playbarPlay', '||');
|
||||
}
|
||||
|
||||
function stopAudioPlayback()
|
||||
function stopAudioPlayback():Void
|
||||
{
|
||||
if (audioInstTrack != null) audioInstTrack.pause();
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
||||
|
||||
setComponentText('playbarPlay', '>');
|
||||
}
|
||||
|
||||
function toggleAudioPlayback()
|
||||
function toggleAudioPlayback():Void
|
||||
{
|
||||
if (audioInstTrack == null) return;
|
||||
|
||||
|
@ -2884,7 +2858,7 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
}
|
||||
|
||||
function handlePlayhead()
|
||||
function handlePlayhead():Void
|
||||
{
|
||||
// Place notes at the playhead.
|
||||
// TODO: Add the ability to switch modes.
|
||||
|
@ -2973,52 +2947,64 @@ class ChartEditorState extends HaxeUIState
|
|||
* Loads an instrumental from an absolute file path, replacing the current instrumental.
|
||||
*
|
||||
* @param path The absolute path to the audio file.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
public function loadInstrumentalFromPath(path:String):Void
|
||||
public function loadInstrumentalFromPath(path:Path):Bool
|
||||
{
|
||||
#if sys
|
||||
// Validate file extension.
|
||||
var fileExtension:String = Path.extension(path);
|
||||
if (!SUPPORTED_MUSIC_FORMATS.contains(fileExtension))
|
||||
if (!SUPPORTED_MUSIC_FORMATS.contains(path.ext))
|
||||
{
|
||||
trace('[WARN] Unsupported file extension: $fileExtension');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path);
|
||||
loadInstrumentalFromBytes(fileBytes);
|
||||
var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadInstrumentalFromBytes(fileBytes, '${path.file}.${path.ext}');
|
||||
#else
|
||||
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an instrumental from audio byte data, replacing the current instrumental.
|
||||
* @param bytes The audio byte data.
|
||||
* @param fileName The name of the file, if available. Used for notifications.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
public function loadInstrumentalFromBytes(bytes:haxe.io.Bytes):Void
|
||||
public function loadInstrumentalFromBytes(bytes:haxe.io.Bytes, fileName:String = null):Bool
|
||||
{
|
||||
var openflSound = new openfl.media.Sound();
|
||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length);
|
||||
audioInstTrack = FlxG.sound.load(openflSound, 1.0, false);
|
||||
audioInstTrack.autoDestroy = false;
|
||||
audioInstTrack.pause();
|
||||
|
||||
// Tell the user the load was successful.
|
||||
// TODO: Un-bork this.
|
||||
// showNotification('Loaded instrumental track successfully.');
|
||||
|
||||
postLoadInstrumental();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function loadInstrumentalFromAsset(path:String):Void
|
||||
/**
|
||||
* Loads an instrumental from an OpenFL asset, replacing the current instrumental.
|
||||
* @param path The path to the asset. Use `Paths` to build this.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
public function loadInstrumentalFromAsset(path:String):Bool
|
||||
{
|
||||
var instTrack = FlxG.sound.load(path, 1.0, false);
|
||||
audioInstTrack = instTrack;
|
||||
var instTrack:FlxSound = FlxG.sound.load(path, 1.0, false);
|
||||
if (instTrack != null)
|
||||
{
|
||||
audioInstTrack = instTrack;
|
||||
|
||||
postLoadInstrumental();
|
||||
postLoadInstrumental();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function postLoadInstrumental()
|
||||
function postLoadInstrumental():Void
|
||||
{
|
||||
// Prevent the time from skipping back to 0 when the song ends.
|
||||
audioInstTrack.onComplete = function() {
|
||||
|
@ -3042,42 +3028,47 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
/**
|
||||
* Loads a vocal track from an absolute file path.
|
||||
* @param path The absolute path to the audio file.
|
||||
* @param charKey The character to load the vocal track for.
|
||||
*/
|
||||
public function loadVocalsFromPath(path:String, ?charKey:String):Void
|
||||
public function loadVocalsFromPath(path:Path, charKey:String = null):Bool
|
||||
{
|
||||
#if sys
|
||||
var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path);
|
||||
loadVocalsFromBytes(fileBytes, charKey);
|
||||
var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadVocalsFromBytes(fileBytes, charKey);
|
||||
#else
|
||||
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
public function loadVocalsFromAsset(path:String, ?charKey:String):Void
|
||||
public function loadVocalsFromAsset(path:String, charKey:String = null):Bool
|
||||
{
|
||||
var vocalTrack:FlxSound = FlxG.sound.load(path, 1.0, false);
|
||||
audioVocalTrackGroup.add(vocalTrack);
|
||||
if (vocalTrack != null)
|
||||
{
|
||||
audioVocalTrackGroup.add(vocalTrack);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a vocal track from audio byte data.
|
||||
*/
|
||||
public function loadVocalsFromBytes(bytes:haxe.io.Bytes, ?charKey:String):Void
|
||||
public function loadVocalsFromBytes(bytes:haxe.io.Bytes, charKey:String = null):Bool
|
||||
{
|
||||
var openflSound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length);
|
||||
var vocalTrack:FlxSound = FlxG.sound.load(openflSound, 1.0, false);
|
||||
audioVocalTrackGroup.add(vocalTrack);
|
||||
|
||||
// Tell the user the load was successful.
|
||||
// TODO: Un-bork this.
|
||||
// showNotification('Loaded instrumental track successfully.');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch's a song's existing chart and audio and loads it, replacing the current song.
|
||||
*/
|
||||
public function loadSongAsTemplate(songId:String)
|
||||
public function loadSongAsTemplate(songId:String):Void
|
||||
{
|
||||
var song:Song = SongDataParser.fetchSong(songId);
|
||||
|
||||
|
@ -3089,6 +3080,7 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
// Load the song metadata.
|
||||
var rawSongMetadata:Array<SongMetadata> = song.getRawMetadata();
|
||||
var songName:String = rawSongMetadata[0].songName;
|
||||
|
||||
this.songMetadata = new Map<String, SongMetadata>();
|
||||
|
||||
|
@ -3112,14 +3104,20 @@ class ChartEditorState extends HaxeUIState
|
|||
loadInstrumentalFromAsset(Paths.inst(songId));
|
||||
loadVocalsFromAsset(Paths.voices(songId));
|
||||
|
||||
// showNotification('Loaded song ${songId}.');
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded song ($songName)',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When setting the scroll position, except when automatically scrolling during song playback,
|
||||
* we need to update the conductor's current step time and the timestamp of the audio tracks.
|
||||
*/
|
||||
function moveSongToScrollPosition()
|
||||
function moveSongToScrollPosition():Void
|
||||
{
|
||||
// Update the songPosition in the Conductor.
|
||||
Conductor.update(scrollPositionInMs);
|
||||
|
@ -3168,7 +3166,7 @@ class ChartEditorState extends HaxeUIState
|
|||
return;
|
||||
}
|
||||
|
||||
var command = undoHistory.pop();
|
||||
var command:ChartEditorCommand = undoHistory.pop();
|
||||
undoCommand(command);
|
||||
}
|
||||
|
||||
|
@ -3183,11 +3181,11 @@ class ChartEditorState extends HaxeUIState
|
|||
return;
|
||||
}
|
||||
|
||||
var command = redoHistory.pop();
|
||||
var command:ChartEditorCommand = redoHistory.pop();
|
||||
performCommand(command, false);
|
||||
}
|
||||
|
||||
function sortChartData()
|
||||
function sortChartData():Void
|
||||
{
|
||||
currentSongChartNoteData.sort(function(a:SongNoteData, b:SongNoteData):Int {
|
||||
return FlxSort.byValues(FlxSort.ASCENDING, a.time, b.time);
|
||||
|
@ -3198,7 +3196,7 @@ class ChartEditorState extends HaxeUIState
|
|||
});
|
||||
}
|
||||
|
||||
function playMetronomeTick(?high:Bool = false)
|
||||
function playMetronomeTick(?high:Bool = false):Void
|
||||
{
|
||||
playSound(Paths.sound('pianoStuff/piano-${high ? '001' : '008'}'));
|
||||
}
|
||||
|
@ -3217,7 +3215,7 @@ class ChartEditorState extends HaxeUIState
|
|||
* Play a sound effect.
|
||||
* Automatically cleans up after itself and recycles previous FlxSound instances if available, for performance.
|
||||
*/
|
||||
function playSound(path:String)
|
||||
function playSound(path:String):Void
|
||||
{
|
||||
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound);
|
||||
snd.loadEmbedded(FlxG.sound.cache(path));
|
||||
|
@ -3226,7 +3224,7 @@ class ChartEditorState extends HaxeUIState
|
|||
snd.play();
|
||||
}
|
||||
|
||||
override function destroy()
|
||||
override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
|
||||
|
@ -3282,7 +3280,14 @@ class ChartEditorState extends HaxeUIState
|
|||
|
||||
if (force)
|
||||
{
|
||||
var targetPath:String = tmp ? Path.join([FileUtil.getTempDir(), 'chart-editor-exit-${DateUtil.generateTimestamp()}.zip']) : Path.join(['./backups/', 'chart-editor-exit-${DateUtil.generateTimestamp()}.zip']);
|
||||
var targetPath:String = if (tmp)
|
||||
{
|
||||
Path.join([FileUtil.getTempDir(), 'chart-editor-exit-${DateUtil.generateTimestamp()}.zip']);
|
||||
}
|
||||
else
|
||||
{
|
||||
Path.join(['./backups/', 'chart-editor-exit-${DateUtil.generateTimestamp()}.zip']);
|
||||
}
|
||||
|
||||
// We have to force write because the program will die before the save dialog is closed.
|
||||
trace('Force exporting to $targetPath...');
|
||||
|
@ -3291,11 +3296,11 @@ class ChartEditorState extends HaxeUIState
|
|||
}
|
||||
|
||||
// Prompt and save.
|
||||
var onSave:Array<String>->Void = (paths:Array<String>) -> {
|
||||
var onSave:Array<String>->Void = function(paths:Array<String>) {
|
||||
trace('Successfully exported files.');
|
||||
};
|
||||
|
||||
var onCancel:Void->Void = () -> {
|
||||
var onCancel:Void->Void = function() {
|
||||
trace('Export cancelled.');
|
||||
};
|
||||
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package funkin.ui.haxeui;
|
||||
|
||||
import haxe.ui.containers.menus.MenuCheckBox;
|
||||
import haxe.ui.components.CheckBox;
|
||||
import haxe.ui.events.DragEvent;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import haxe.ui.containers.menus.MenuCheckBox;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import lime.app.Application;
|
||||
|
||||
class HaxeUIState extends MusicBeatState
|
||||
|
@ -23,7 +21,7 @@ class HaxeUIState extends MusicBeatState
|
|||
_componentKey = key;
|
||||
}
|
||||
|
||||
override function create()
|
||||
override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
|
@ -31,7 +29,7 @@ class HaxeUIState extends MusicBeatState
|
|||
if (component != null) add(component);
|
||||
}
|
||||
|
||||
public function buildComponent(assetPath:String)
|
||||
public function buildComponent(assetPath:String):Component
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -81,15 +79,13 @@ class HaxeUIState extends MusicBeatState
|
|||
{
|
||||
if (target == null)
|
||||
{
|
||||
Screen.instance.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent)
|
||||
{
|
||||
Screen.instance.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent) {
|
||||
showContextMenu(assetPath, e.screenX, e.screenY);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
target.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent)
|
||||
{
|
||||
target.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent) {
|
||||
showContextMenu(assetPath, e.screenX, e.screenY);
|
||||
});
|
||||
}
|
||||
|
@ -98,7 +94,7 @@ class HaxeUIState extends MusicBeatState
|
|||
/**
|
||||
* Add an onClick listener to a HaxeUI menu bar item.
|
||||
*/
|
||||
function addUIClickListener(key:String, callback:MouseEvent->Void)
|
||||
function addUIClickListener(key:String, callback:MouseEvent->Void):Void
|
||||
{
|
||||
var target:Component = findComponent(key);
|
||||
if (target == null)
|
||||
|
@ -112,10 +108,24 @@ class HaxeUIState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
function setComponentText(key:String, text:String):Void
|
||||
{
|
||||
var target:Component = findComponent(key);
|
||||
if (target == null)
|
||||
{
|
||||
// Gracefully handle the case where the item can't be located.
|
||||
trace('WARN: Could not locate menu item: $key');
|
||||
}
|
||||
else
|
||||
{
|
||||
target.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an onChange listener to a HaxeUI input component such as a slider or text field.
|
||||
*/
|
||||
function addUIChangeListener(key:String, callback:UIEvent->Void)
|
||||
function addUIChangeListener(key:String, callback:UIEvent->Void):Void
|
||||
{
|
||||
var target:Component = findComponent(key);
|
||||
if (target == null)
|
||||
|
@ -179,7 +189,7 @@ class HaxeUIState extends MusicBeatState
|
|||
return component.findComponent(criteria, type, recursive, searchType);
|
||||
}
|
||||
|
||||
override function destroy()
|
||||
override function destroy():Void
|
||||
{
|
||||
if (component != null) remove(component);
|
||||
component = null;
|
||||
|
|
Loading…
Reference in a new issue