2023-10-26 09:46:22 +00:00
|
|
|
|
package funkin.ui.debug.charting.handlers;
|
2022-10-26 05:14:29 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
import flixel.util.FlxTimer;
|
|
|
|
|
import funkin.data.song.importer.FNFLegacyData;
|
|
|
|
|
import funkin.data.song.importer.FNFLegacyImporter;
|
|
|
|
|
import funkin.data.song.SongData.SongCharacterData;
|
2023-09-08 21:46:44 +00:00
|
|
|
|
import funkin.data.song.SongData.SongChartData;
|
|
|
|
|
import funkin.data.song.SongData.SongMetadata;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
import funkin.data.song.SongData.SongTimeChange;
|
|
|
|
|
import funkin.data.song.SongRegistry;
|
2022-11-26 01:48:05 +00:00
|
|
|
|
import funkin.input.Cursor;
|
2022-12-17 20:19:42 +00:00
|
|
|
|
import funkin.play.character.BaseCharacter;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
import funkin.play.character.CharacterData;
|
2022-12-17 20:19:42 +00:00
|
|
|
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
2023-03-01 02:06:09 +00:00
|
|
|
|
import funkin.play.song.Song;
|
2024-01-16 21:49:15 +00:00
|
|
|
|
import funkin.data.stage.StageData;
|
2023-11-24 05:41:38 +00:00
|
|
|
|
import funkin.ui.debug.charting.dialogs.ChartEditorAboutDialog;
|
2023-11-24 05:49:51 +00:00
|
|
|
|
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget;
|
2023-12-12 10:24:43 +00:00
|
|
|
|
import funkin.ui.debug.charting.dialogs.ChartEditorCharacterIconSelectorMenu;
|
2023-11-24 05:41:38 +00:00
|
|
|
|
import funkin.ui.debug.charting.dialogs.ChartEditorUploadChartDialog;
|
|
|
|
|
import funkin.ui.debug.charting.dialogs.ChartEditorWelcomeDialog;
|
2024-01-18 04:53:23 +00:00
|
|
|
|
import funkin.ui.debug.charting.dialogs.ChartEditorUploadVocalsDialog;
|
2023-10-26 09:46:22 +00:00
|
|
|
|
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
import funkin.util.Constants;
|
2023-11-24 05:49:51 +00:00
|
|
|
|
import funkin.util.DateUtil;
|
2023-06-08 20:48:34 +00:00
|
|
|
|
import funkin.util.FileUtil;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
import funkin.util.SerializerUtil;
|
|
|
|
|
import funkin.util.SortUtil;
|
|
|
|
|
import funkin.util.VersionUtil;
|
2023-11-23 00:17:35 +00:00
|
|
|
|
import funkin.util.WindowUtil;
|
2023-03-01 02:06:09 +00:00
|
|
|
|
import haxe.io.Path;
|
2022-12-01 22:32:10 +00:00
|
|
|
|
import haxe.ui.components.Button;
|
|
|
|
|
import haxe.ui.components.DropDown;
|
2022-12-17 20:19:42 +00:00
|
|
|
|
import haxe.ui.components.Label;
|
2022-11-26 01:48:05 +00:00
|
|
|
|
import haxe.ui.components.Link;
|
2022-12-01 22:32:10 +00:00
|
|
|
|
import haxe.ui.components.NumberStepper;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
import haxe.ui.components.Slider;
|
2022-12-01 22:32:10 +00:00
|
|
|
|
import haxe.ui.components.TextField;
|
|
|
|
|
import haxe.ui.containers.Box;
|
2022-11-25 00:09:47 +00:00
|
|
|
|
import haxe.ui.containers.dialogs.Dialog;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
2022-12-01 22:32:10 +00:00
|
|
|
|
import haxe.ui.containers.dialogs.Dialogs;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
import haxe.ui.containers.Form;
|
2023-12-12 10:24:43 +00:00
|
|
|
|
import haxe.ui.containers.menus.Menu;
|
2022-11-25 00:09:47 +00:00
|
|
|
|
import haxe.ui.containers.VBox;
|
2023-03-01 02:06:09 +00:00
|
|
|
|
import haxe.ui.core.Component;
|
2022-12-01 22:32:10 +00:00
|
|
|
|
import haxe.ui.events.UIEvent;
|
2023-03-01 02:06:09 +00:00
|
|
|
|
import haxe.ui.notifications.NotificationManager;
|
|
|
|
|
import haxe.ui.notifications.NotificationType;
|
2023-11-24 05:41:38 +00:00
|
|
|
|
import haxe.ui.RuntimeComponentBuilder;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
import thx.semver.Version;
|
2022-12-01 22:32:10 +00:00
|
|
|
|
|
|
|
|
|
using Lambda;
|
2022-11-25 00:09:47 +00:00
|
|
|
|
|
2023-02-28 18:17:28 +00:00
|
|
|
|
/**
|
|
|
|
|
* Handles dialogs for the new Chart Editor.
|
|
|
|
|
*/
|
2023-08-30 06:24:35 +00:00
|
|
|
|
@:nullSafety
|
2023-10-26 09:46:22 +00:00
|
|
|
|
@:access(funkin.ui.debug.charting.ChartEditorState)
|
2022-10-26 05:14:29 +00:00
|
|
|
|
class ChartEditorDialogHandler
|
|
|
|
|
{
|
2023-10-26 09:46:22 +00:00
|
|
|
|
// Paths to HaxeUI layout files for each dialog.
|
2023-02-28 18:17:28 +00:00
|
|
|
|
static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-inst');
|
|
|
|
|
static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata');
|
2023-10-21 05:04:50 +00:00
|
|
|
|
static final CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-parts');
|
|
|
|
|
static final CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-parts-entry');
|
2023-06-08 20:48:34 +00:00
|
|
|
|
static final CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/import-chart');
|
2023-02-28 18:17:28 +00:00
|
|
|
|
static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
static final CHART_EDITOR_DIALOG_ADD_VARIATION_LAYOUT:String = Paths.ui('chart-editor/dialogs/add-variation');
|
|
|
|
|
static final CHART_EDITOR_DIALOG_ADD_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/dialogs/add-difficulty');
|
2023-11-23 00:17:35 +00:00
|
|
|
|
static final CHART_EDITOR_DIALOG_BACKUP_AVAILABLE_LAYOUT:String = Paths.ui('chart-editor/dialogs/backup-available');
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2023-03-01 02:06:09 +00:00
|
|
|
|
* 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.
|
2023-01-23 03:25:45 +00:00
|
|
|
|
*/
|
2023-10-26 09:46:22 +00:00
|
|
|
|
public static function openAboutDialog(state:ChartEditorState):Null<Dialog>
|
2023-01-23 03:25:45 +00:00
|
|
|
|
{
|
2023-11-24 05:41:38 +00:00
|
|
|
|
var dialog = ChartEditorAboutDialog.build(state);
|
|
|
|
|
|
|
|
|
|
dialog.zIndex = 1000;
|
|
|
|
|
state.isHaxeUIDialogOpen = true;
|
|
|
|
|
|
|
|
|
|
return dialog;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Builds and opens a dialog letting the user create a new chart, open a recent chart, or load from a template.
|
2023-03-01 02:06:09 +00:00
|
|
|
|
* @param state The current chart editor state.
|
|
|
|
|
* @param closable Whether the dialog can be closed by the user.
|
|
|
|
|
* @return The dialog that was opened.
|
2023-01-23 03:25:45 +00:00
|
|
|
|
*/
|
2023-08-31 22:47:23 +00:00
|
|
|
|
public static function openWelcomeDialog(state:ChartEditorState, closable:Bool = true):Null<Dialog>
|
2023-01-23 03:25:45 +00:00
|
|
|
|
{
|
2023-11-24 05:41:38 +00:00
|
|
|
|
var dialog = ChartEditorWelcomeDialog.build(state, closable);
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
dialog.zIndex = 1000;
|
2023-10-22 19:43:39 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = true;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
2023-09-30 02:29:32 +00:00
|
|
|
|
state.fadeInWelcomeMusic();
|
2023-11-24 05:41:38 +00:00
|
|
|
|
|
2023-01-23 03:25:45 +00:00
|
|
|
|
return dialog;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-18 04:53:23 +00:00
|
|
|
|
/**
|
|
|
|
|
* Builds and opens a dialog letting the user browse for a chart file to open.
|
|
|
|
|
* @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 openBrowseFNFC(state:ChartEditorState, closable:Bool):Null<Dialog>
|
|
|
|
|
{
|
|
|
|
|
var dialog = ChartEditorUploadChartDialog.build(state, closable);
|
|
|
|
|
|
|
|
|
|
dialog.zIndex = 1000;
|
|
|
|
|
state.isHaxeUIDialogOpen = true;
|
|
|
|
|
|
|
|
|
|
return dialog;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 charData:SongCharacterData = state.currentSongMetadata.playData.characters;
|
|
|
|
|
|
|
|
|
|
var hasClearedVocals:Bool = false;
|
|
|
|
|
|
|
|
|
|
var charIdsForVocals:Array<String> = [charData.player, charData.opponent];
|
|
|
|
|
|
|
|
|
|
var dialog = ChartEditorUploadVocalsDialog.build(state, charIdsForVocals, closable);
|
|
|
|
|
|
|
|
|
|
dialog.zIndex = 1000;
|
|
|
|
|
state.isHaxeUIDialogOpen = true;
|
|
|
|
|
|
|
|
|
|
return dialog;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Builds and opens the dialog for selecting a character.
|
|
|
|
|
*/
|
|
|
|
|
public static function openCharacterDropdown(state:ChartEditorState, charType:CharacterType, lockPosition:Bool = false):Null<Menu>
|
|
|
|
|
{
|
|
|
|
|
var menu = ChartEditorCharacterIconSelectorMenu.build(state, charType, lockPosition);
|
|
|
|
|
|
|
|
|
|
menu.zIndex = 1000;
|
|
|
|
|
|
|
|
|
|
return menu;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 18:31:02 +00:00
|
|
|
|
/**
|
2023-11-24 05:58:12 +00:00
|
|
|
|
* Builds and opens a dialog letting the user know a backup is available, and prompting them to load it.
|
2023-11-21 18:31:02 +00:00
|
|
|
|
*/
|
2023-11-23 00:17:35 +00:00
|
|
|
|
public static function openBackupAvailableDialog(state:ChartEditorState, welcomeDialog:Null<Dialog>):Null<Dialog>
|
2023-11-21 18:31:02 +00:00
|
|
|
|
{
|
|
|
|
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_BACKUP_AVAILABLE_LAYOUT, true, true);
|
|
|
|
|
if (dialog == null) throw 'Could not locate Backup Available dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
dialog.onDialogClosed = function(event) {
|
2023-11-23 00:17:35 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-11-23 00:17:35 +00:00
|
|
|
|
{
|
|
|
|
|
// User loaded the backup! Close the welcome dialog behind this.
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (welcomeDialog != null) welcomeDialog.hideDialog(DialogButton.APPLY);
|
2023-11-23 00:17:35 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the dialog, don't close the welcome dialog so we aren't in a broken state.
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-11-21 18:31:02 +00:00
|
|
|
|
|
|
|
|
|
state.isHaxeUIDialogOpen = true;
|
|
|
|
|
|
2023-11-23 00:17:35 +00:00
|
|
|
|
var backupTimeLabel:Null<Label> = dialog.findComponent('backupTimeLabel', Label);
|
|
|
|
|
if (backupTimeLabel == null) throw 'Could not locate backupTimeLabel button in Backup Available dialog';
|
|
|
|
|
|
|
|
|
|
var latestBackupDate:Null<Date> = ChartEditorImportExportHandler.getLatestBackupDate();
|
|
|
|
|
if (latestBackupDate != null)
|
|
|
|
|
{
|
|
|
|
|
var latestBackupDateStr:String = DateUtil.generateCleanTimestamp(latestBackupDate);
|
|
|
|
|
backupTimeLabel.text = latestBackupDateStr;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 18:31:02 +00:00
|
|
|
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
|
|
|
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Backup Available dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonCancel.onClick = function(_) {
|
2023-11-23 00:17:35 +00:00
|
|
|
|
// Don't hide the welcome dialog behind this.
|
2023-11-21 18:31:02 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.CANCEL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buttonGoToFolder:Null<Button> = dialog.findComponent('buttonGoToFolder', Button);
|
|
|
|
|
if (buttonGoToFolder == null) throw 'Could not locate buttonGoToFolder button in Backup Available dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonGoToFolder.onClick = function(_) {
|
2023-11-23 00:42:28 +00:00
|
|
|
|
state.openBackupsFolder();
|
2023-11-23 00:17:35 +00:00
|
|
|
|
// Don't hide the welcome dialog behind this.
|
2023-11-29 01:36:59 +00:00
|
|
|
|
// Don't close this dialog.
|
2023-11-21 18:31:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buttonOpenBackup:Null<Button> = dialog.findComponent('buttonOpenBackup', Button);
|
|
|
|
|
if (buttonOpenBackup == null) throw 'Could not locate buttonOpenBackup button in Backup Available dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonOpenBackup.onClick = function(_) {
|
2023-11-23 00:17:35 +00:00
|
|
|
|
var latestBackupPath:Null<String> = ChartEditorImportExportHandler.getLatestBackupPath();
|
|
|
|
|
|
|
|
|
|
var result:Null<Array<String>> = (latestBackupPath != null) ? state.loadFromFNFCPath(latestBackupPath) : null;
|
|
|
|
|
if (result != null)
|
|
|
|
|
{
|
|
|
|
|
if (result.length == 0)
|
|
|
|
|
{
|
|
|
|
|
// No warnings.
|
|
|
|
|
state.success('Loaded Chart', 'Loaded chart (${latestBackupPath})');
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// One or more warnings.
|
|
|
|
|
state.warning('Loaded Chart', 'Loaded chart (${latestBackupPath})\n${result.join("\n")}');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close the welcome dialog behind this.
|
|
|
|
|
dialog.hideDialog(DialogButton.APPLY);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
state.error('Failed to Load Chart', 'Failed to load chart (${latestBackupPath})');
|
|
|
|
|
|
|
|
|
|
// Song failed to load, don't close the Welcome dialog so we aren't in a broken state.
|
|
|
|
|
dialog.hideDialog(DialogButton.CANCEL);
|
|
|
|
|
}
|
2023-11-21 18:31:02 +00:00
|
|
|
|
}
|
2023-11-23 00:17:35 +00:00
|
|
|
|
|
|
|
|
|
return dialog;
|
2023-11-21 18:31:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-08 20:48:34 +00:00
|
|
|
|
/**
|
|
|
|
|
* Open the wizard for opening an existing chart from individual files.
|
|
|
|
|
* @param state
|
|
|
|
|
* @param closable
|
|
|
|
|
*/
|
|
|
|
|
public static function openBrowseWizard(state:ChartEditorState, closable:Bool):Void
|
|
|
|
|
{
|
|
|
|
|
// Open the "Open Chart" wizard
|
|
|
|
|
// Step 1. Open Chart
|
|
|
|
|
var openChartDialog:Dialog = openChartDialog(state);
|
2023-11-29 01:36:59 +00:00
|
|
|
|
openChartDialog.onDialogClosed = function(event) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 2. Upload instrumental
|
|
|
|
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadInstDialog.onDialogClosed = function(event) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 3. Upload Vocals
|
|
|
|
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
|
|
|
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadVocalsDialog.onDialogClosed = function(event) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-10-24 19:50:02 +00:00
|
|
|
|
state.currentWorkingFilePath = null; // Built from parts, so no .fnfc to save to.
|
|
|
|
|
state.switchToCurrentInstrumental();
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.postLoadInstrumental();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function openImportChartWizard(state:ChartEditorState, format:String, closable:Bool):Void
|
|
|
|
|
{
|
|
|
|
|
// Open the "Open Chart" wizard
|
|
|
|
|
// Step 1. Open Chart
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var openChartDialog:Null<Dialog> = openImportChartDialog(state, format);
|
|
|
|
|
if (openChartDialog == null) throw 'Could not locate Import Chart dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
openChartDialog.onDialogClosed = function(event) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 2. Upload instrumental
|
|
|
|
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadInstDialog.onDialogClosed = function(event) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 3. Upload Vocals
|
|
|
|
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
|
|
|
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadVocalsDialog.onDialogClosed = function(_) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-10-24 19:50:02 +00:00
|
|
|
|
state.currentWorkingFilePath = null; // New file, so no path.
|
|
|
|
|
state.switchToCurrentInstrumental();
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.postLoadInstrumental();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 07:12:34 +00:00
|
|
|
|
public static function openCreateSongWizardBasicOnly(state:ChartEditorState, closable:Bool):Void
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
2023-09-26 21:46:07 +00:00
|
|
|
|
// Step 1. Song Metadata
|
2023-11-29 01:36:59 +00:00
|
|
|
|
var songMetadataDialog:Dialog = openSongMetadataDialog(state, false, Constants.DEFAULT_VARIATION, true);
|
|
|
|
|
songMetadataDialog.onDialogClosed = function(event) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
2023-09-26 21:46:07 +00:00
|
|
|
|
// Step 2. Upload Instrumental
|
|
|
|
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadInstDialog.onDialogClosed = function(event) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 3. Upload Vocals
|
|
|
|
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
2023-09-26 21:46:07 +00:00
|
|
|
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadVocalsDialog.onDialogClosed = function(_) {
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-10-23 16:22:29 +00:00
|
|
|
|
state.currentWorkingFilePath = null; // New file, so no path.
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.switchToCurrentInstrumental();
|
|
|
|
|
state.postLoadInstrumental();
|
|
|
|
|
}
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-09-26 21:46:07 +00:00
|
|
|
|
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-09-26 21:46:07 +00:00
|
|
|
|
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-09-26 21:46:07 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 07:12:34 +00:00
|
|
|
|
public static function openCreateSongWizardErectOnly(state:ChartEditorState, closable:Bool):Void
|
2023-09-26 21:46:07 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 1. Song Metadata
|
2023-11-29 01:36:59 +00:00
|
|
|
|
var songMetadataDialog:Dialog = openSongMetadataDialog(state, true, Constants.DEFAULT_VARIATION, true);
|
|
|
|
|
songMetadataDialog.onDialogClosed = function(event) {
|
2023-11-21 07:12:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-11-21 07:12:34 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 2. Upload Instrumental
|
|
|
|
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadInstDialog.onDialogClosed = function(event) {
|
2023-11-21 07:12:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-11-21 07:12:34 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 3. Upload Vocals
|
|
|
|
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
|
|
|
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadVocalsDialog.onDialogClosed = function(_) {
|
2023-11-21 07:12:34 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
|
|
|
|
state.currentWorkingFilePath = null; // New file, so no path.
|
|
|
|
|
state.switchToCurrentInstrumental();
|
|
|
|
|
state.postLoadInstrumental();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-11-21 07:12:34 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-11-21 07:12:34 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function openCreateSongWizardBasicErect(state:ChartEditorState, closable:Bool):Void
|
|
|
|
|
{
|
|
|
|
|
// Step 1. Song Metadata
|
2023-11-29 01:36:59 +00:00
|
|
|
|
var songMetadataDialog:Dialog = openSongMetadataDialog(state, false, Constants.DEFAULT_VARIATION, true);
|
|
|
|
|
songMetadataDialog.onDialogClosed = function(event) {
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-09-26 21:46:07 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 2. Upload Instrumental
|
|
|
|
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadInstDialog.onDialogClosed = function(event) {
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-09-26 21:46:07 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 3. Upload Vocals
|
|
|
|
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
|
|
|
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadVocalsDialog.onDialogClosed = function(_) {
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.switchToCurrentInstrumental();
|
|
|
|
|
// Step 4. Song Metadata (Erect)
|
2023-11-29 01:36:59 +00:00
|
|
|
|
var songMetadataDialogErect:Dialog = openSongMetadataDialog(state, true, 'erect', false);
|
|
|
|
|
songMetadataDialogErect.onDialogClosed = function(event) {
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-09-26 21:46:07 +00:00
|
|
|
|
{
|
|
|
|
|
// Switch to the Erect variation so uploading the instrumental applies properly.
|
|
|
|
|
state.selectedVariation = 'erect';
|
|
|
|
|
|
|
|
|
|
// Step 5. Upload Instrumental (Erect)
|
|
|
|
|
var uploadInstDialogErect:Dialog = openUploadInstDialog(state, closable);
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadInstDialogErect.onDialogClosed = function(event) {
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
if (event.button == DialogButton.APPLY)
|
2023-09-26 21:46:07 +00:00
|
|
|
|
{
|
|
|
|
|
// Step 6. Upload Vocals (Erect)
|
|
|
|
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
|
|
|
|
var uploadVocalsDialogErect:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
2023-11-29 01:36:59 +00:00
|
|
|
|
uploadVocalsDialogErect.onDialogClosed = function(_) {
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-10-23 16:22:29 +00:00
|
|
|
|
state.currentWorkingFilePath = null; // New file, so no path.
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.switchToCurrentInstrumental();
|
|
|
|
|
state.postLoadInstrumental();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard at Step 5! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-09-26 21:46:07 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard at Step 4! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-09-26 21:46:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-09-26 21:46:07 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.openWelcomeDialog(closable);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-01 02:06:09 +00:00
|
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2023-08-30 22:31:59 +00:00
|
|
|
|
@:haxe.warning("-WVarInit") // Hide the warning about the onDropFile handler.
|
2023-08-28 19:03:29 +00:00
|
|
|
|
public static function openUploadInstDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
2023-01-23 03:25:45 +00:00
|
|
|
|
{
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT, true, closable);
|
|
|
|
|
if (dialog == null) throw 'Could not locate Upload Instrumental dialog';
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
|
|
|
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Instrumental dialog';
|
2023-03-01 02:06:09 +00:00
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonCancel.onClick = function(_) {
|
2023-03-01 02:06:09 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.CANCEL);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var instrumentalBox:Null<Box> = dialog.findComponent('instrumentalBox', Box);
|
|
|
|
|
if (instrumentalBox == null) throw 'Could not locate instrumentalBox in Upload Instrumental dialog';
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
instrumentalBox.onMouseOver = function(_) {
|
2023-01-23 03:25:45 +00:00
|
|
|
|
instrumentalBox.swapClass('upload-bg', 'upload-bg-hover');
|
|
|
|
|
Cursor.cursorMode = Pointer;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
instrumentalBox.onMouseOut = function(_) {
|
2023-01-23 03:25:45 +00:00
|
|
|
|
instrumentalBox.swapClass('upload-bg-hover', 'upload-bg');
|
|
|
|
|
Cursor.cursorMode = Default;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 21:46:07 +00:00
|
|
|
|
var instId:String = state.currentInstrumentalId;
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
var dropHandler:DialogDropTarget = {component: instrumentalBox, handler: null};
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
instrumentalBox.onClick = function(_) {
|
2023-03-01 02:06:09 +00:00
|
|
|
|
Dialogs.openBinaryFile('Open Instrumental', [
|
|
|
|
|
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
|
2023-08-31 22:47:23 +00:00
|
|
|
|
if (selectedFile != null && selectedFile.bytes != null)
|
2023-02-28 18:17:28 +00:00
|
|
|
|
{
|
2023-10-26 09:46:22 +00:00
|
|
|
|
if (state.loadInstFromBytes(selectedFile.bytes, instId))
|
2023-03-01 02:06:09 +00:00
|
|
|
|
{
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Loaded Instrumental', 'Loaded instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})');
|
2023-03-01 02:06:09 +00:00
|
|
|
|
|
2023-10-18 05:02:10 +00:00
|
|
|
|
state.switchToCurrentInstrumental();
|
2023-03-01 02:06:09 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.APPLY);
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.removeDropHandler(dropHandler);
|
2023-03-01 02:06:09 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failed to Load Instrumental', 'Failed to load instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})');
|
2023-03-01 02:06:09 +00:00
|
|
|
|
}
|
2023-02-28 18:17:28 +00:00
|
|
|
|
}
|
2023-01-23 03:25:45 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
var onDropFile:String->Void = function(pathStr:String) {
|
2023-03-01 02:06:09 +00:00
|
|
|
|
var path:Path = new Path(pathStr);
|
|
|
|
|
trace('Dropped file (${path})');
|
2023-10-26 09:46:22 +00:00
|
|
|
|
if (state.loadInstFromPath(path, instId))
|
2023-03-01 02:06:09 +00:00
|
|
|
|
{
|
|
|
|
|
// Tell the user the load was successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Loaded Instrumental', 'Loaded instrumental track (${path.file}.${path.ext}) for variation (${state.selectedVariation})');
|
2023-03-01 02:06:09 +00:00
|
|
|
|
|
2023-10-18 05:02:10 +00:00
|
|
|
|
state.switchToCurrentInstrumental();
|
2023-03-01 02:06:09 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.APPLY);
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.removeDropHandler(dropHandler);
|
2023-03-01 02:06:09 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var message:String = if (!ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext ?? ''))
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
|
|
|
|
'File format (${path.ext}) not supported for instrumental track (${path.file}.${path.ext})';
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-09-26 21:46:07 +00:00
|
|
|
|
'Failed to load instrumental track (${path.file}.${path.ext}) for variation (${state.selectedVariation})';
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-03-01 02:06:09 +00:00
|
|
|
|
// Tell the user the load was successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failed to Load Instrumental', message);
|
2023-03-01 02:06:09 +00:00
|
|
|
|
}
|
2023-01-23 03:25:45 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
dropHandler.handler = onDropFile;
|
|
|
|
|
|
|
|
|
|
state.addDropHandler(dropHandler);
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
|
|
return dialog;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-28 18:17:28 +00:00
|
|
|
|
/**
|
|
|
|
|
* Opens the dialog in the wizard where the user can set song metadata like name and artist and BPM.
|
|
|
|
|
* @param state The ChartEditorState instance.
|
2023-11-29 01:36:59 +00:00
|
|
|
|
* @param erect Whether to create erect difficulties or normal ones.
|
|
|
|
|
* @param targetVariation The variation to create difficulties for.
|
|
|
|
|
* @param clearExistingMetadata Whether to clear existing metadata when confirming.
|
2023-02-28 18:17:28 +00:00
|
|
|
|
* @return The dialog to open.
|
|
|
|
|
*/
|
2023-08-22 08:27:30 +00:00
|
|
|
|
@:haxe.warning("-WVarInit")
|
2023-11-29 01:36:59 +00:00
|
|
|
|
public static function openSongMetadataDialog(state:ChartEditorState, erect:Bool, targetVariation:String, clearExistingMetadata:Bool):Dialog
|
2023-01-23 03:25:45 +00:00
|
|
|
|
{
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
|
|
|
|
|
if (dialog == null) throw 'Could not locate Song Metadata dialog';
|
2023-03-01 02:06:09 +00:00
|
|
|
|
|
2023-09-26 21:46:07 +00:00
|
|
|
|
if (targetVariation != Constants.DEFAULT_VARIATION)
|
|
|
|
|
{
|
2023-10-15 04:22:25 +00:00
|
|
|
|
dialog.title = 'New Chart - Provide Song Metadata (${targetVariation.toTitleCase()})';
|
2023-09-26 21:46:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
|
|
|
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Song Metadata dialog';
|
2023-10-22 19:43:39 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = true;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonCancel.onClick = function(_) {
|
2023-10-22 19:43:39 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-03-01 02:06:09 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.CANCEL);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-21 07:12:34 +00:00
|
|
|
|
var newSongMetadata:SongMetadata = new SongMetadata('', '', Constants.DEFAULT_VARIATION);
|
2023-09-26 03:24:07 +00:00
|
|
|
|
|
2023-11-21 07:12:34 +00:00
|
|
|
|
newSongMetadata.variation = targetVariation;
|
|
|
|
|
newSongMetadata.playData.difficulties = (erect) ? ['erect', 'nightmare'] : ['easy', 'normal', 'hard'];
|
2023-10-18 05:02:10 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var inputSongName:Null<TextField> = dialog.findComponent('inputSongName', TextField);
|
|
|
|
|
if (inputSongName == null) throw 'Could not locate inputSongName TextField in Song Metadata dialog';
|
|
|
|
|
inputSongName.onChange = function(event:UIEvent) {
|
2023-02-28 18:17:28 +00:00
|
|
|
|
var valid:Bool = event.target.text != null && event.target.text != '';
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
|
|
if (valid)
|
|
|
|
|
{
|
2023-09-26 03:24:07 +00:00
|
|
|
|
inputSongName.removeClass('invalid-value');
|
|
|
|
|
newSongMetadata.songName = event.target.text;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-09-26 03:24:07 +00:00
|
|
|
|
newSongMetadata.songName = "";
|
2023-01-23 03:25:45 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
2023-09-26 03:24:07 +00:00
|
|
|
|
inputSongName.text = "";
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var inputSongArtist:Null<TextField> = dialog.findComponent('inputSongArtist', TextField);
|
|
|
|
|
if (inputSongArtist == null) throw 'Could not locate inputSongArtist TextField in Song Metadata dialog';
|
|
|
|
|
inputSongArtist.onChange = function(event:UIEvent) {
|
2023-02-28 18:17:28 +00:00
|
|
|
|
var valid:Bool = event.target.text != null && event.target.text != '';
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
|
|
if (valid)
|
|
|
|
|
{
|
2023-09-26 03:24:07 +00:00
|
|
|
|
inputSongArtist.removeClass('invalid-value');
|
|
|
|
|
newSongMetadata.artist = event.target.text;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-09-26 03:24:07 +00:00
|
|
|
|
newSongMetadata.artist = "";
|
2023-01-23 03:25:45 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
2023-09-26 03:24:07 +00:00
|
|
|
|
inputSongArtist.text = "";
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var inputStage:Null<DropDown> = dialog.findComponent('inputStage', DropDown);
|
|
|
|
|
if (inputStage == null) throw 'Could not locate inputStage DropDown in Song Metadata dialog';
|
|
|
|
|
inputStage.onChange = function(event:UIEvent) {
|
2023-03-01 02:06:09 +00:00
|
|
|
|
if (event.data == null && event.data.id == null) return;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
newSongMetadata.playData.stage = event.data.id;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
};
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var startingValueStage = ChartEditorDropdowns.populateDropdownWithStages(inputStage, newSongMetadata.playData.stage);
|
|
|
|
|
inputStage.value = startingValueStage;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
2023-10-31 18:43:01 +00:00
|
|
|
|
var inputNoteStyle:Null<DropDown> = dialog.findComponent('inputNoteStyle', DropDown);
|
2023-09-26 03:24:07 +00:00
|
|
|
|
if (inputNoteStyle == null) throw 'Could not locate inputNoteStyle DropDown in Song Metadata dialog';
|
|
|
|
|
inputNoteStyle.onChange = function(event:UIEvent) {
|
2023-01-23 03:25:45 +00:00
|
|
|
|
if (event.data.id == null) return;
|
2023-10-21 05:04:50 +00:00
|
|
|
|
newSongMetadata.playData.noteStyle = event.data.id;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
};
|
2023-10-21 05:04:50 +00:00
|
|
|
|
var startingValueNoteStyle = ChartEditorDropdowns.populateDropdownWithNoteStyles(inputNoteStyle, newSongMetadata.playData.noteStyle);
|
2023-09-26 03:24:07 +00:00
|
|
|
|
inputNoteStyle.value = startingValueNoteStyle;
|
|
|
|
|
|
2023-10-31 18:43:01 +00:00
|
|
|
|
var inputCharacterPlayer:Null<DropDown> = dialog.findComponent('inputCharacterPlayer', DropDown);
|
2023-09-26 03:24:07 +00:00
|
|
|
|
if (inputCharacterPlayer == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterPlayer component.';
|
|
|
|
|
inputCharacterPlayer.onChange = function(event:UIEvent) {
|
|
|
|
|
if (event.data?.id == null) return;
|
|
|
|
|
newSongMetadata.playData.characters.player = event.data.id;
|
|
|
|
|
};
|
|
|
|
|
var startingValuePlayer = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterPlayer, CharacterType.BF,
|
|
|
|
|
newSongMetadata.playData.characters.player);
|
|
|
|
|
inputCharacterPlayer.value = startingValuePlayer;
|
|
|
|
|
|
2023-10-31 18:43:01 +00:00
|
|
|
|
var inputCharacterOpponent:Null<DropDown> = dialog.findComponent('inputCharacterOpponent', DropDown);
|
2023-09-26 03:24:07 +00:00
|
|
|
|
if (inputCharacterOpponent == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterOpponent component.';
|
|
|
|
|
inputCharacterOpponent.onChange = function(event:UIEvent) {
|
|
|
|
|
if (event.data?.id == null) return;
|
|
|
|
|
newSongMetadata.playData.characters.opponent = event.data.id;
|
|
|
|
|
};
|
|
|
|
|
var startingValueOpponent = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterOpponent, CharacterType.DAD,
|
|
|
|
|
newSongMetadata.playData.characters.opponent);
|
|
|
|
|
inputCharacterOpponent.value = startingValueOpponent;
|
|
|
|
|
|
2023-10-31 18:43:01 +00:00
|
|
|
|
var inputCharacterGirlfriend:Null<DropDown> = dialog.findComponent('inputCharacterGirlfriend', DropDown);
|
2023-09-26 03:24:07 +00:00
|
|
|
|
if (inputCharacterGirlfriend == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterGirlfriend component.';
|
|
|
|
|
inputCharacterGirlfriend.onChange = function(event:UIEvent) {
|
|
|
|
|
if (event.data?.id == null) return;
|
|
|
|
|
newSongMetadata.playData.characters.girlfriend = event.data.id == "none" ? "" : event.data.id;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
};
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var startingValueGirlfriend = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterGirlfriend, CharacterType.GF,
|
|
|
|
|
newSongMetadata.playData.characters.girlfriend);
|
|
|
|
|
inputCharacterGirlfriend.value = startingValueGirlfriend;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var dialogBPM:Null<NumberStepper> = dialog.findComponent('dialogBPM', NumberStepper);
|
|
|
|
|
if (dialogBPM == null) throw 'Could not locate dialogBPM NumberStepper in Song Metadata dialog';
|
2023-03-01 02:06:09 +00:00
|
|
|
|
dialogBPM.onChange = function(event:UIEvent) {
|
2023-01-23 03:25:45 +00:00
|
|
|
|
if (event.value == null || event.value <= 0) return;
|
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var timeChanges:Array<SongTimeChange> = newSongMetadata.timeChanges;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
if (timeChanges == null || timeChanges.length == 0)
|
|
|
|
|
{
|
2023-09-08 21:46:44 +00:00
|
|
|
|
timeChanges = [new SongTimeChange(0, event.value)];
|
2023-01-23 03:25:45 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
timeChanges[0].bpm = event.value;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
newSongMetadata.timeChanges = timeChanges;
|
2023-01-23 03:25:45 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
|
|
|
|
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Song Metadata dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
dialogContinue.onClick = (_) -> {
|
|
|
|
|
if (clearExistingMetadata)
|
|
|
|
|
{
|
|
|
|
|
state.songMetadata.clear();
|
|
|
|
|
state.songChartData.clear();
|
|
|
|
|
}
|
2023-11-21 07:12:34 +00:00
|
|
|
|
|
2023-09-26 21:46:07 +00:00
|
|
|
|
state.songMetadata.set(targetVariation, newSongMetadata);
|
|
|
|
|
|
2023-12-14 21:56:20 +00:00
|
|
|
|
Conductor.instance.instrumentalOffset = state.currentInstrumentalOffset; // Loads from the metadata.
|
|
|
|
|
Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges);
|
2024-01-05 07:35:41 +00:00
|
|
|
|
state.updateTimeSignature();
|
2023-10-17 20:57:06 +00:00
|
|
|
|
|
2023-12-19 06:25:15 +00:00
|
|
|
|
state.selectedVariation = Constants.DEFAULT_VARIATION;
|
|
|
|
|
state.selectedDifficulty = state.availableDifficulties[0];
|
|
|
|
|
|
2023-11-21 07:12:34 +00:00
|
|
|
|
state.difficultySelectDirty = true;
|
|
|
|
|
|
2023-09-26 21:46:07 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.APPLY);
|
|
|
|
|
}
|
2023-01-23 03:25:45 +00:00
|
|
|
|
|
|
|
|
|
return dialog;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-08 20:48:34 +00:00
|
|
|
|
/**
|
|
|
|
|
* Builds and opens a dialog where the user upload the JSON files for a 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.
|
|
|
|
|
*/
|
|
|
|
|
@:haxe.warning('-WVarInit')
|
2023-08-28 19:03:29 +00:00
|
|
|
|
public static function openChartDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
2023-10-21 05:04:50 +00:00
|
|
|
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_LAYOUT, true, closable);
|
2023-08-31 22:47:23 +00:00
|
|
|
|
if (dialog == null) throw 'Could not locate Open Chart dialog';
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
|
|
|
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Open Chart dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonCancel.onClick = function(_) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.CANCEL);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var chartContainerA:Null<Component> = dialog.findComponent('chartContainerA');
|
|
|
|
|
if (chartContainerA == null) throw 'Could not locate chartContainerA in Open Chart dialog';
|
|
|
|
|
var chartContainerB:Null<Component> = dialog.findComponent('chartContainerB');
|
|
|
|
|
if (chartContainerB == null) throw 'Could not locate chartContainerB in Open Chart dialog';
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
var songMetadata:Map<String, SongMetadata> = [];
|
|
|
|
|
var songChartData:Map<String, SongChartData> = [];
|
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var buttonContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
|
|
|
|
if (buttonContinue == null) throw 'Could not locate dialogContinue button in Open Chart dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonContinue.onClick = function(_) {
|
2023-10-26 09:46:22 +00:00
|
|
|
|
state.loadSong(songMetadata, songChartData);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
dialog.hideDialog(DialogButton.APPLY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var onDropFileMetadataVariation:String->Label->String->Void;
|
|
|
|
|
var onClickMetadataVariation:String->Label->UIEvent->Void;
|
|
|
|
|
var onDropFileChartDataVariation:String->Label->String->Void;
|
|
|
|
|
var onClickChartDataVariation:String->Label->UIEvent->Void;
|
|
|
|
|
|
|
|
|
|
var constructVariationEntries:Array<String>->Void = function(variations:Array<String>) {
|
|
|
|
|
// Clear the chart container.
|
|
|
|
|
while (chartContainerB.getComponentAt(0) != null)
|
|
|
|
|
{
|
|
|
|
|
chartContainerB.removeComponent(chartContainerB.getComponentAt(0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build an entry for -chart.json.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
var songDefaultChartDataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var songDefaultChartDataEntryLabel:Null<Label> = songDefaultChartDataEntry.findComponent('chartEntryLabel', Label);
|
|
|
|
|
if (songDefaultChartDataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#if FILE_DROP_SUPPORTED
|
2023-06-08 20:48:34 +00:00
|
|
|
|
songDefaultChartDataEntryLabel.text = 'Drag and drop <song>-chart.json file, or click to browse.';
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#else
|
|
|
|
|
songDefaultChartDataEntryLabel.text = 'Click to browse for <song>-chart.json file.';
|
|
|
|
|
#end
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
songDefaultChartDataEntry.onClick = onClickChartDataVariation.bind(Constants.DEFAULT_VARIATION).bind(songDefaultChartDataEntryLabel);
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.addDropHandler(
|
|
|
|
|
{
|
|
|
|
|
component: songDefaultChartDataEntry,
|
|
|
|
|
handler: onDropFileChartDataVariation.bind(Constants.DEFAULT_VARIATION).bind(songDefaultChartDataEntryLabel)
|
|
|
|
|
});
|
2023-06-08 20:48:34 +00:00
|
|
|
|
chartContainerB.addComponent(songDefaultChartDataEntry);
|
|
|
|
|
|
|
|
|
|
for (variation in variations)
|
|
|
|
|
{
|
|
|
|
|
// Build entries for -metadata-<variation>.json.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
var songVariationMetadataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var songVariationMetadataEntryLabel:Null<Label> = songVariationMetadataEntry.findComponent('chartEntryLabel', Label);
|
|
|
|
|
if (songVariationMetadataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#if FILE_DROP_SUPPORTED
|
2023-06-08 20:48:34 +00:00
|
|
|
|
songVariationMetadataEntryLabel.text = 'Drag and drop <song>-metadata-${variation}.json file, or click to browse.';
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#else
|
|
|
|
|
songVariationMetadataEntryLabel.text = 'Click to browse for <song>-metadata-${variation}.json file.';
|
|
|
|
|
#end
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
songVariationMetadataEntry.onMouseOver = function(_) {
|
2023-09-12 22:37:59 +00:00
|
|
|
|
songVariationMetadataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
|
|
|
|
Cursor.cursorMode = Pointer;
|
|
|
|
|
}
|
2023-11-29 01:36:59 +00:00
|
|
|
|
songVariationMetadataEntry.onMouseOut = function(_) {
|
2023-09-12 22:37:59 +00:00
|
|
|
|
songVariationMetadataEntry.swapClass('upload-bg-hover', 'upload-bg');
|
|
|
|
|
Cursor.cursorMode = Default;
|
|
|
|
|
}
|
2023-06-08 20:48:34 +00:00
|
|
|
|
songVariationMetadataEntry.onClick = onClickMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel);
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#if FILE_DROP_SUPPORTED
|
2024-03-13 21:38:00 +00:00
|
|
|
|
state.addDropHandler({component: songVariationMetadataEntry, handler: onDropFileMetadataVariation.bind(variation)
|
|
|
|
|
.bind(songVariationMetadataEntryLabel)});
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#end
|
2023-06-08 20:48:34 +00:00
|
|
|
|
chartContainerB.addComponent(songVariationMetadataEntry);
|
|
|
|
|
|
|
|
|
|
// Build entries for -chart-<variation>.json.
|
2023-11-24 05:41:38 +00:00
|
|
|
|
var songVariationChartDataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var songVariationChartDataEntryLabel:Null<Label> = songVariationChartDataEntry.findComponent('chartEntryLabel', Label);
|
|
|
|
|
if (songVariationChartDataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#if FILE_DROP_SUPPORTED
|
2023-06-08 20:48:34 +00:00
|
|
|
|
songVariationChartDataEntryLabel.text = 'Drag and drop <song>-chart-${variation}.json file, or click to browse.';
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#else
|
|
|
|
|
songVariationChartDataEntryLabel.text = 'Click to browse for <song>-chart-${variation}.json file.';
|
|
|
|
|
#end
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
songVariationChartDataEntry.onMouseOver = function(_) {
|
2023-09-12 22:37:59 +00:00
|
|
|
|
songVariationChartDataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
|
|
|
|
Cursor.cursorMode = Pointer;
|
|
|
|
|
}
|
2023-11-29 01:36:59 +00:00
|
|
|
|
songVariationChartDataEntry.onMouseOut = function(_) {
|
2023-09-12 22:37:59 +00:00
|
|
|
|
songVariationChartDataEntry.swapClass('upload-bg-hover', 'upload-bg');
|
|
|
|
|
Cursor.cursorMode = Default;
|
|
|
|
|
}
|
2023-06-08 20:48:34 +00:00
|
|
|
|
songVariationChartDataEntry.onClick = onClickChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel);
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#if FILE_DROP_SUPPORTED
|
2024-03-13 21:38:00 +00:00
|
|
|
|
state.addDropHandler(
|
|
|
|
|
{
|
|
|
|
|
component: songVariationChartDataEntry,
|
|
|
|
|
handler: onDropFileChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel)
|
|
|
|
|
});
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#end
|
2023-06-08 20:48:34 +00:00
|
|
|
|
chartContainerB.addComponent(songVariationChartDataEntry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDropFileMetadataVariation = function(variation:String, label:Label, pathStr:String) {
|
|
|
|
|
var path:Path = new Path(pathStr);
|
|
|
|
|
trace('Dropped JSON file (${path})');
|
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var songMetadataTxt:String = FileUtil.readStringFromPath(path.toString());
|
|
|
|
|
|
|
|
|
|
var songMetadataVersion:Null<Version> = VersionUtil.getVersionFromJSON(songMetadataTxt);
|
|
|
|
|
if (songMetadataVersion == null)
|
|
|
|
|
{
|
|
|
|
|
// Tell the user the load was not successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failure', 'Could not parse metadata file version (${path.file}.${path.ext})');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var songMetadataVariation:Null<SongMetadata> = SongRegistry.instance.parseEntryMetadataRawWithMigration(songMetadataTxt, path.toString(),
|
|
|
|
|
songMetadataVersion);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-08-28 19:03:29 +00:00
|
|
|
|
if (songMetadataVariation == null)
|
|
|
|
|
{
|
|
|
|
|
// Tell the user the load was not successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failure', 'Could not load metadata file (${path.file}.${path.ext})');
|
2023-08-28 19:03:29 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-08 20:48:34 +00:00
|
|
|
|
songMetadata.set(variation, songMetadataVariation);
|
|
|
|
|
|
|
|
|
|
// Tell the user the load was successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Loaded Metadata', 'Loaded metadata file (${path.file}.${path.ext})');
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#if FILE_DROP_SUPPORTED
|
2023-06-08 20:48:34 +00:00
|
|
|
|
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#else
|
|
|
|
|
label.text = 'Metadata file (click to browse)\n${path.file}.${path.ext}';
|
|
|
|
|
#end
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations);
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
onClickMetadataVariation = function(variation:String, label:Label, _:UIEvent) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [
|
|
|
|
|
{label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) {
|
2023-08-31 22:47:23 +00:00
|
|
|
|
if (selectedFile != null && selectedFile.bytes != null)
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
|
|
|
|
trace('Selected file: ' + selectedFile.name);
|
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var songMetadataTxt:String = selectedFile.bytes.toString();
|
|
|
|
|
|
|
|
|
|
var songMetadataVersion:Null<Version> = VersionUtil.getVersionFromJSON(songMetadataTxt);
|
|
|
|
|
if (songMetadataVersion == null)
|
|
|
|
|
{
|
|
|
|
|
// Tell the user the load was not successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failure', 'Could not parse metadata file version (${selectedFile.name})');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var songMetadataVariation:Null<SongMetadata> = SongRegistry.instance.parseEntryMetadataRawWithMigration(songMetadataTxt, selectedFile.name,
|
|
|
|
|
songMetadataVersion);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
if (songMetadataVariation != null)
|
|
|
|
|
{
|
|
|
|
|
songMetadata.set(variation, songMetadataVariation);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
// Tell the user the load was successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Loaded Metadata', 'Loaded metadata file (${selectedFile.name})');
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
#if FILE_DROP_SUPPORTED
|
|
|
|
|
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
|
|
|
|
#else
|
|
|
|
|
label.text = 'Metadata file (click to browse)\n${selectedFile.name}';
|
|
|
|
|
#end
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Tell the user the load was unsuccessful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failure', 'Failed to load metadata file (${selectedFile.name})');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
}
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDropFileChartDataVariation = function(variation:String, label:Label, pathStr:String) {
|
|
|
|
|
var path:Path = new Path(pathStr);
|
|
|
|
|
trace('Dropped JSON file (${path})');
|
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var songChartDataTxt:String = FileUtil.readStringFromPath(path.toString());
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var songChartDataVersion:Null<Version> = VersionUtil.getVersionFromJSON(songChartDataTxt);
|
|
|
|
|
if (songChartDataVersion == null)
|
|
|
|
|
{
|
|
|
|
|
// Tell the user the load was not successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failure', 'Could not parse chart data file version (${path.file}.${path.ext})');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var songChartDataVariation:Null<SongChartData> = SongRegistry.instance.parseEntryChartDataRawWithMigration(songChartDataTxt, path.toString(),
|
|
|
|
|
songChartDataVersion);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
if (songChartDataVariation != null)
|
|
|
|
|
{
|
|
|
|
|
songChartData.set(variation, songChartDataVariation);
|
|
|
|
|
state.notePreviewDirty = true;
|
|
|
|
|
state.notePreviewViewportBoundsDirty = true;
|
|
|
|
|
state.noteDisplayDirty = true;
|
|
|
|
|
|
|
|
|
|
// Tell the user the load was successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Loaded Chart Data', 'Loaded chart data file (${path.file}.${path.ext})');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
|
|
|
|
|
#if FILE_DROP_SUPPORTED
|
|
|
|
|
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
|
|
|
|
#else
|
|
|
|
|
label.text = 'Chart data file (click to browse)\n${path.file}.${path.ext}';
|
|
|
|
|
#end
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Tell the user the load was unsuccessful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failure', 'Failed to load chart data file (${path.file}.${path.ext})');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
}
|
2023-06-08 20:48:34 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
onClickChartDataVariation = function(variation:String, label:Label, _:UIEvent) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [
|
|
|
|
|
{label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) {
|
2023-08-31 22:47:23 +00:00
|
|
|
|
if (selectedFile != null && selectedFile.bytes != null)
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
|
|
|
|
trace('Selected file: ' + selectedFile.name);
|
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var songChartDataTxt:String = selectedFile.bytes.toString();
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var songChartDataVersion:Null<Version> = VersionUtil.getVersionFromJSON(songChartDataTxt);
|
|
|
|
|
if (songChartDataVersion == null)
|
|
|
|
|
{
|
|
|
|
|
// Tell the user the load was not successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failure', 'Could not parse chart data file version (${selectedFile.name})');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var songChartDataVariation:Null<SongChartData> = SongRegistry.instance.parseEntryChartDataRawWithMigration(songChartDataTxt, selectedFile.name,
|
|
|
|
|
songChartDataVersion);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
if (songChartDataVariation != null)
|
|
|
|
|
{
|
|
|
|
|
songChartData.set(variation, songChartDataVariation);
|
|
|
|
|
state.notePreviewDirty = true;
|
|
|
|
|
state.notePreviewViewportBoundsDirty = true;
|
|
|
|
|
state.noteDisplayDirty = true;
|
|
|
|
|
|
|
|
|
|
// Tell the user the load was successful.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Loaded Chart Data', 'Loaded chart data file (${selectedFile.name})');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
|
|
|
|
|
#if FILE_DROP_SUPPORTED
|
|
|
|
|
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
|
|
|
|
#else
|
|
|
|
|
label.text = 'Chart data file (click to browse)\n${selectedFile.name}';
|
|
|
|
|
#end
|
|
|
|
|
}
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
var metadataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var metadataEntryLabel:Null<Label> = metadataEntry.findComponent('chartEntryLabel', Label);
|
|
|
|
|
if (metadataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
2023-09-08 21:41:20 +00:00
|
|
|
|
|
|
|
|
|
#if FILE_DROP_SUPPORTED
|
2023-06-08 20:48:34 +00:00
|
|
|
|
metadataEntryLabel.text = 'Drag and drop <song>-metadata.json file, or click to browse.';
|
2023-09-08 21:41:20 +00:00
|
|
|
|
#else
|
|
|
|
|
metadataEntryLabel.text = 'Click to browse for <song>-metadata.json file.';
|
|
|
|
|
#end
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
metadataEntry.onClick = onClickMetadataVariation.bind(Constants.DEFAULT_VARIATION).bind(metadataEntryLabel);
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.addDropHandler({component: metadataEntry, handler: onDropFileMetadataVariation.bind(Constants.DEFAULT_VARIATION).bind(metadataEntryLabel)});
|
2023-09-12 22:37:59 +00:00
|
|
|
|
metadataEntry.onMouseOver = function(_event) {
|
|
|
|
|
metadataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
|
|
|
|
Cursor.cursorMode = Pointer;
|
|
|
|
|
}
|
2023-11-29 01:36:59 +00:00
|
|
|
|
metadataEntry.onMouseOut = function(_) {
|
2023-09-12 22:37:59 +00:00
|
|
|
|
metadataEntry.swapClass('upload-bg-hover', 'upload-bg');
|
|
|
|
|
Cursor.cursorMode = Default;
|
|
|
|
|
}
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
chartContainerA.addComponent(metadataEntry);
|
|
|
|
|
|
|
|
|
|
return dialog;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Builds and opens a dialog where the user can import a chart from an existing file format.
|
|
|
|
|
* @param state The current chart editor state.
|
|
|
|
|
* @param format The format to import from.
|
|
|
|
|
* @param closable
|
|
|
|
|
* @return Dialog
|
|
|
|
|
*/
|
2023-08-31 22:47:23 +00:00
|
|
|
|
public static function openImportChartDialog(state:ChartEditorState, format:String, closable:Bool = true):Null<Dialog>
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT, true, closable);
|
|
|
|
|
if (dialog == null) return null;
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
var prettyFormat:String = switch (format)
|
|
|
|
|
{
|
|
|
|
|
case 'legacy': 'FNF Legacy';
|
|
|
|
|
default: 'Unknown';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var fileFilter = switch (format)
|
|
|
|
|
{
|
|
|
|
|
case 'legacy': {label: 'JSON Data File (.json)', extension: 'json'};
|
|
|
|
|
default: null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dialog.title = 'Import Chart - ${prettyFormat}';
|
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
|
|
|
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Import Chart dialog';
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-10-22 19:43:39 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = true;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonCancel.onClick = function(_) {
|
2023-10-22 19:43:39 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-06-08 20:48:34 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.CANCEL);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 22:47:23 +00:00
|
|
|
|
var importBox:Null<Box> = dialog.findComponent('importBox', Box);
|
|
|
|
|
if (importBox == null) throw 'Could not locate importBox in Import Chart dialog';
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
importBox.onMouseOver = function(_) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
importBox.swapClass('upload-bg', 'upload-bg-hover');
|
|
|
|
|
Cursor.cursorMode = Pointer;
|
|
|
|
|
}
|
2023-11-29 01:36:59 +00:00
|
|
|
|
importBox.onMouseOut = function(_) {
|
2023-06-08 20:48:34 +00:00
|
|
|
|
importBox.swapClass('upload-bg-hover', 'upload-bg');
|
|
|
|
|
Cursor.cursorMode = Default;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var onDropFile:String->Void;
|
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
importBox.onClick = function(_) {
|
2023-08-31 22:47:23 +00:00
|
|
|
|
Dialogs.openBinaryFile('Import Chart - ${prettyFormat}', fileFilter != null ? [fileFilter] : [], function(selectedFile:SelectedFileInfo) {
|
|
|
|
|
if (selectedFile != null && selectedFile.bytes != null)
|
2023-06-08 20:48:34 +00:00
|
|
|
|
{
|
|
|
|
|
trace('Selected file: ' + selectedFile.fullPath);
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var selectedFileTxt:String = selectedFile.bytes.toString();
|
|
|
|
|
var fnfLegacyData:Null<FNFLegacyData> = FNFLegacyImporter.parseLegacyDataRaw(selectedFileTxt, selectedFile.fullPath);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
if (fnfLegacyData == null)
|
|
|
|
|
{
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.error('Failure', 'Failed to parse FNF chart file (${selectedFile.name})');
|
2023-09-26 03:24:07 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var songMetadata:SongMetadata = FNFLegacyImporter.migrateMetadata(fnfLegacyData);
|
|
|
|
|
var songChartData:SongChartData = FNFLegacyImporter.migrateChartData(fnfLegacyData);
|
|
|
|
|
|
2023-10-26 09:46:22 +00:00
|
|
|
|
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
dialog.hideDialog(DialogButton.APPLY);
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Success', 'Loaded chart file (${selectedFile.name})');
|
2023-06-08 20:48:34 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDropFile = function(pathStr:String) {
|
|
|
|
|
var path:Path = new Path(pathStr);
|
2023-09-26 03:24:07 +00:00
|
|
|
|
var selectedFileText:String = FileUtil.readStringFromPath(path.toString());
|
|
|
|
|
var selectedFileData:FNFLegacyData = FNFLegacyImporter.parseLegacyDataRaw(selectedFileText, path.toString());
|
|
|
|
|
var songMetadata:SongMetadata = FNFLegacyImporter.migrateMetadata(selectedFileData);
|
|
|
|
|
var songChartData:SongChartData = FNFLegacyImporter.migrateChartData(selectedFileData);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
2023-10-26 09:46:22 +00:00
|
|
|
|
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
dialog.hideDialog(DialogButton.APPLY);
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Success', 'Loaded chart file (${path.file}.${path.ext})');
|
2023-06-08 20:48:34 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
state.addDropHandler({component: importBox, handler: onDropFile});
|
2023-06-08 20:48:34 +00:00
|
|
|
|
|
|
|
|
|
return dialog;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 03:25:45 +00:00
|
|
|
|
/**
|
|
|
|
|
* Builds and opens a dialog displaying the user guide, providing guidance and help on how to use the chart editor.
|
2023-06-08 20:30:45 +00:00
|
|
|
|
*
|
2023-03-01 02:06:09 +00:00
|
|
|
|
* @param state The current chart editor state.
|
|
|
|
|
* @return The dialog that was opened.
|
2023-01-23 03:25:45 +00:00
|
|
|
|
*/
|
2023-10-26 09:46:22 +00:00
|
|
|
|
public static function openUserGuideDialog(state:ChartEditorState):Null<Dialog>
|
2023-01-23 03:25:45 +00:00
|
|
|
|
{
|
|
|
|
|
return openDialog(state, CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT, true, true);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
/**
|
|
|
|
|
* Builds and opens a dialog where the user can add a new variation for a 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 openAddVariationDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
|
|
|
|
{
|
|
|
|
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_ADD_VARIATION_LAYOUT, true, false);
|
|
|
|
|
if (dialog == null) throw 'Could not locate Add Variation dialog';
|
|
|
|
|
|
|
|
|
|
var variationForm:Null<Form> = dialog.findComponent('variationForm', Form);
|
|
|
|
|
if (variationForm == null) throw 'Could not locate variationForm Form in Add Variation dialog';
|
|
|
|
|
|
|
|
|
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
|
|
|
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Add Variation dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonCancel.onClick = function(_) {
|
2023-09-26 03:24:07 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.CANCEL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buttonAdd:Null<Button> = dialog.findComponent('dialogAdd', Button);
|
|
|
|
|
if (buttonAdd == null) throw 'Could not locate dialogAdd button in Add Variation dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonAdd.onClick = function(_) {
|
2023-09-26 03:24:07 +00:00
|
|
|
|
// This performs validation before the onSubmit callback is called.
|
|
|
|
|
variationForm.submit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dialogSongName:Null<TextField> = dialog.findComponent('dialogSongName', TextField);
|
|
|
|
|
if (dialogSongName == null) throw 'Could not locate dialogSongName TextField in Add Variation dialog';
|
|
|
|
|
dialogSongName.value = state.currentSongMetadata.songName;
|
|
|
|
|
|
|
|
|
|
var dialogSongArtist:Null<TextField> = dialog.findComponent('dialogSongArtist', TextField);
|
|
|
|
|
if (dialogSongArtist == null) throw 'Could not locate dialogSongArtist TextField in Add Variation dialog';
|
|
|
|
|
dialogSongArtist.value = state.currentSongMetadata.artist;
|
|
|
|
|
|
|
|
|
|
var dialogStage:Null<DropDown> = dialog.findComponent('dialogStage', DropDown);
|
|
|
|
|
if (dialogStage == null) throw 'Could not locate dialogStage DropDown in Add Variation dialog';
|
|
|
|
|
var startingValueStage = ChartEditorDropdowns.populateDropdownWithStages(dialogStage, state.currentSongMetadata.playData.stage);
|
|
|
|
|
dialogStage.value = startingValueStage;
|
|
|
|
|
|
|
|
|
|
var dialogNoteStyle:Null<DropDown> = dialog.findComponent('dialogNoteStyle', DropDown);
|
|
|
|
|
if (dialogNoteStyle == null) throw 'Could not locate dialogNoteStyle DropDown in Add Variation dialog';
|
2023-10-21 05:04:50 +00:00
|
|
|
|
dialogNoteStyle.value = state.currentSongMetadata.playData.noteStyle;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
|
|
|
|
|
var dialogCharacterPlayer:Null<DropDown> = dialog.findComponent('dialogCharacterPlayer', DropDown);
|
|
|
|
|
if (dialogCharacterPlayer == null) throw 'Could not locate dialogCharacterPlayer DropDown in Add Variation dialog';
|
|
|
|
|
dialogCharacterPlayer.value = ChartEditorDropdowns.populateDropdownWithCharacters(dialogCharacterPlayer, CharacterType.BF,
|
|
|
|
|
state.currentSongMetadata.playData.characters.player);
|
|
|
|
|
|
|
|
|
|
var dialogCharacterOpponent:Null<DropDown> = dialog.findComponent('dialogCharacterOpponent', DropDown);
|
|
|
|
|
if (dialogCharacterOpponent == null) throw 'Could not locate dialogCharacterOpponent DropDown in Add Variation dialog';
|
|
|
|
|
dialogCharacterOpponent.value = ChartEditorDropdowns.populateDropdownWithCharacters(dialogCharacterOpponent, CharacterType.DAD,
|
|
|
|
|
state.currentSongMetadata.playData.characters.opponent);
|
|
|
|
|
|
|
|
|
|
var dialogCharacterGirlfriend:Null<DropDown> = dialog.findComponent('dialogCharacterGirlfriend', DropDown);
|
|
|
|
|
if (dialogCharacterGirlfriend == null) throw 'Could not locate dialogCharacterGirlfriend DropDown in Add Variation dialog';
|
|
|
|
|
dialogCharacterGirlfriend.value = ChartEditorDropdowns.populateDropdownWithCharacters(dialogCharacterGirlfriend, CharacterType.GF,
|
|
|
|
|
state.currentSongMetadata.playData.characters.girlfriend);
|
|
|
|
|
|
|
|
|
|
var dialogBPM:Null<NumberStepper> = dialog.findComponent('dialogBPM', NumberStepper);
|
|
|
|
|
if (dialogBPM == null) throw 'Could not locate dialogBPM NumberStepper in Add Variation dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
var currentStartingBPM:Float = state.currentSongMetadata.timeChanges[0].bpm;
|
|
|
|
|
dialogBPM.value = currentStartingBPM;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
|
|
|
|
|
// If all validators succeeded, this callback is called.
|
|
|
|
|
|
2023-10-22 19:43:39 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = true;
|
2023-11-29 01:36:59 +00:00
|
|
|
|
variationForm.onSubmit = function(_) {
|
2023-10-22 19:43:39 +00:00
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
trace('Add Variation dialog submitted, validation succeeded!');
|
|
|
|
|
|
|
|
|
|
var dialogVariationName:Null<TextField> = dialog.findComponent('dialogVariationName', TextField);
|
|
|
|
|
if (dialogVariationName == null) throw 'Could not locate dialogVariationName TextField in Add Variation dialog';
|
|
|
|
|
|
|
|
|
|
var pendingVariation:SongMetadata = new SongMetadata(dialogSongName.text, dialogSongArtist.text, dialogVariationName.text.toLowerCase());
|
|
|
|
|
|
|
|
|
|
pendingVariation.playData.stage = dialogStage.value.id;
|
2023-10-21 05:04:50 +00:00
|
|
|
|
pendingVariation.playData.noteStyle = dialogNoteStyle.value;
|
2023-09-26 03:24:07 +00:00
|
|
|
|
pendingVariation.timeChanges[0].bpm = dialogBPM.value;
|
|
|
|
|
|
|
|
|
|
state.songMetadata.set(pendingVariation.variation, pendingVariation);
|
|
|
|
|
state.difficultySelectDirty = true; // Force the Difficulty toolbox to update.
|
2023-11-23 00:16:38 +00:00
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
// Don't update conductor since we haven't switched to the new variation yet.
|
|
|
|
|
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Add Variation', 'Added new variation "${pendingVariation.variation}"');
|
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.APPLY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dialog;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Builds and opens a dialog where the user can add a new difficulty for a 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 openAddDifficultyDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
|
|
|
|
{
|
|
|
|
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_ADD_DIFFICULTY_LAYOUT, true, false);
|
|
|
|
|
if (dialog == null) throw 'Could not locate Add Difficulty dialog';
|
|
|
|
|
|
|
|
|
|
var difficultyForm:Null<Form> = dialog.findComponent('difficultyForm', Form);
|
|
|
|
|
if (difficultyForm == null) throw 'Could not locate difficultyForm Form in Add Difficulty dialog';
|
|
|
|
|
|
|
|
|
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
|
|
|
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Add Difficulty dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonCancel.onClick = function(_) {
|
2023-09-26 03:24:07 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.CANCEL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var buttonAdd:Null<Button> = dialog.findComponent('dialogAdd', Button);
|
|
|
|
|
if (buttonAdd == null) throw 'Could not locate dialogAdd button in Add Difficulty dialog';
|
2023-11-29 01:36:59 +00:00
|
|
|
|
buttonAdd.onClick = function(_) {
|
2023-09-26 03:24:07 +00:00
|
|
|
|
// This performs validation before the onSubmit callback is called.
|
|
|
|
|
difficultyForm.submit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dialogVariation:Null<DropDown> = dialog.findComponent('dialogVariation', DropDown);
|
|
|
|
|
if (dialogVariation == null) throw 'Could not locate dialogVariation DropDown in Add Variation dialog';
|
|
|
|
|
dialogVariation.value = ChartEditorDropdowns.populateDropdownWithVariations(dialogVariation, state, true);
|
|
|
|
|
|
|
|
|
|
var labelScrollSpeed:Null<Label> = dialog.findComponent('labelScrollSpeed', Label);
|
|
|
|
|
if (labelScrollSpeed == null) throw 'Could not find labelScrollSpeed component.';
|
|
|
|
|
|
|
|
|
|
var inputScrollSpeed:Null<Slider> = dialog.findComponent('inputScrollSpeed', Slider);
|
|
|
|
|
if (inputScrollSpeed == null) throw 'Could not find inputScrollSpeed component.';
|
|
|
|
|
inputScrollSpeed.onChange = function(event:UIEvent) {
|
|
|
|
|
labelScrollSpeed.text = 'Scroll Speed: ${inputScrollSpeed.value}x';
|
|
|
|
|
};
|
|
|
|
|
inputScrollSpeed.value = state.currentSongChartScrollSpeed;
|
|
|
|
|
labelScrollSpeed.text = 'Scroll Speed: ${inputScrollSpeed.value}x';
|
|
|
|
|
|
2023-11-29 01:36:59 +00:00
|
|
|
|
difficultyForm.onSubmit = function(_) {
|
2023-09-26 03:24:07 +00:00
|
|
|
|
trace('Add Difficulty dialog submitted, validation succeeded!');
|
|
|
|
|
|
|
|
|
|
var dialogDifficultyName:Null<TextField> = dialog.findComponent('dialogDifficultyName', TextField);
|
|
|
|
|
if (dialogDifficultyName == null) throw 'Could not locate dialogDifficultyName TextField in Add Difficulty dialog';
|
|
|
|
|
|
|
|
|
|
state.createDifficulty(dialogVariation.value.id, dialogDifficultyName.text.toLowerCase(), inputScrollSpeed.value ?? 1.0);
|
|
|
|
|
|
2023-11-23 00:16:38 +00:00
|
|
|
|
state.success('Add Difficulty', 'Added new difficulty "${dialogDifficultyName.text.toLowerCase()}"');
|
|
|
|
|
|
2023-09-26 03:24:07 +00:00
|
|
|
|
dialog.hideDialog(DialogButton.APPLY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dialog;
|
|
|
|
|
}
|
2023-10-26 09:46:22 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Builds and opens a dialog from a given layout path.
|
|
|
|
|
* @param modal Makes the background uninteractable while the dialog is open.
|
|
|
|
|
* @param closable Hides the close button on the dialog, preventing it from being closed unless the user interacts with the dialog.
|
|
|
|
|
*/
|
|
|
|
|
static function openDialog(state:ChartEditorState, key:String, modal:Bool = true, closable:Bool = true):Null<Dialog>
|
|
|
|
|
{
|
2023-11-24 05:41:38 +00:00
|
|
|
|
var dialog:Null<Dialog> = cast RuntimeComponentBuilder.fromAsset(key);
|
2023-10-26 09:46:22 +00:00
|
|
|
|
if (dialog == null) return null;
|
|
|
|
|
|
|
|
|
|
dialog.destroyOnClose = true;
|
|
|
|
|
dialog.closable = closable;
|
|
|
|
|
dialog.showDialog(modal);
|
|
|
|
|
|
|
|
|
|
state.isHaxeUIDialogOpen = true;
|
|
|
|
|
dialog.onDialogClosed = function(event:UIEvent) {
|
|
|
|
|
state.isHaxeUIDialogOpen = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dialog.zIndex = 1000;
|
|
|
|
|
|
|
|
|
|
return dialog;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
// ===============
|
|
|
|
|
// DROP HANDLERS
|
|
|
|
|
// ===============
|
|
|
|
|
static var dropHandlers:Array<DialogDropTarget> = [];
|
2023-10-26 09:46:22 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a callback for when a file is dropped on a component.
|
|
|
|
|
*
|
|
|
|
|
* On OS X you can’t drop on the application window, but rather only the app icon
|
|
|
|
|
* (either in the dock while running or the icon on the hard drive) so this must be disabled
|
|
|
|
|
* and UI updated appropriately.
|
|
|
|
|
*/
|
2023-11-24 05:41:38 +00:00
|
|
|
|
public static function addDropHandler(state:ChartEditorState, dropTarget:DialogDropTarget):Void
|
2023-10-26 09:46:22 +00:00
|
|
|
|
{
|
|
|
|
|
#if desktop
|
|
|
|
|
if (!FlxG.stage.window.onDropFile.has(onDropFile)) FlxG.stage.window.onDropFile.add(onDropFile);
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
dropHandlers.push(dropTarget);
|
2023-10-26 09:46:22 +00:00
|
|
|
|
#else
|
|
|
|
|
trace('addDropHandler not implemented for this platform');
|
|
|
|
|
#end
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
/**
|
|
|
|
|
* Remove a callback for when a file is dropped on a component.
|
|
|
|
|
*/
|
|
|
|
|
public static function removeDropHandler(state:ChartEditorState, dropTarget:DialogDropTarget):Void
|
2023-10-26 09:46:22 +00:00
|
|
|
|
{
|
|
|
|
|
#if desktop
|
2023-11-24 05:41:38 +00:00
|
|
|
|
dropHandlers.remove(dropTarget);
|
2023-10-26 09:46:22 +00:00
|
|
|
|
#end
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
/**
|
|
|
|
|
* Clear ALL drop handlers, including the core handler.
|
|
|
|
|
* Call this only when leaving the chart editor entirely.
|
|
|
|
|
*/
|
|
|
|
|
public static function clearDropHandlers(state:ChartEditorState):Void
|
2023-10-26 09:46:22 +00:00
|
|
|
|
{
|
|
|
|
|
#if desktop
|
|
|
|
|
dropHandlers = [];
|
|
|
|
|
FlxG.stage.window.onDropFile.remove(onDropFile);
|
|
|
|
|
#end
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-24 05:41:38 +00:00
|
|
|
|
static final EPSILON:Float = 0.01;
|
|
|
|
|
|
2023-10-26 09:46:22 +00:00
|
|
|
|
static function onDropFile(path:String):Void
|
|
|
|
|
{
|
|
|
|
|
// a VERY short timer to wait for the mouse position to update
|
2023-11-24 05:41:38 +00:00
|
|
|
|
new FlxTimer().start(EPSILON, function(_) {
|
2023-10-26 09:46:22 +00:00
|
|
|
|
for (handler in dropHandlers)
|
|
|
|
|
{
|
|
|
|
|
if (handler.component.hitTest(FlxG.mouse.screenX, FlxG.mouse.screenY))
|
|
|
|
|
{
|
|
|
|
|
handler.handler(path);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-10-26 05:14:29 +00:00
|
|
|
|
}
|