package funkin.ui.debug.charting.handlers; import flixel.util.FlxTimer; import funkin.data.song.importer.FNFLegacyData; import funkin.data.song.importer.FNFLegacyImporter; import funkin.data.song.SongData.SongCharacterData; import funkin.data.song.SongData.SongChartData; import funkin.data.song.SongData.SongMetadata; import funkin.data.song.SongData.SongTimeChange; import funkin.data.song.SongRegistry; import funkin.input.Cursor; import funkin.play.character.BaseCharacter; import funkin.play.character.CharacterData; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.song.Song; import funkin.play.stage.StageData; import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.util.Constants; import funkin.util.FileUtil; import funkin.util.SerializerUtil; import funkin.util.SortUtil; import funkin.util.VersionUtil; import haxe.io.Path; import haxe.ui.components.Button; import haxe.ui.components.DropDown; import haxe.ui.components.Label; import haxe.ui.components.Link; import haxe.ui.components.NumberStepper; import haxe.ui.components.Slider; import haxe.ui.components.TextField; import haxe.ui.containers.Box; import haxe.ui.containers.dialogs.Dialog; import haxe.ui.containers.dialogs.Dialog.DialogButton; import haxe.ui.containers.dialogs.Dialogs; import haxe.ui.containers.Form; import haxe.ui.containers.VBox; import haxe.ui.core.Component; import haxe.ui.events.UIEvent; import thx.semver.Version; using Lambda; /** * Handles dialogs for the new Chart Editor. */ @:nullSafety @:access(funkin.ui.debug.charting.ChartEditorState) class ChartEditorDialogHandler { // Paths to HaxeUI layout files for each dialog. static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT:String = Paths.ui('chart-editor/dialogs/about'); static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT:String = Paths.ui('chart-editor/dialogs/welcome'); static final CHART_EDITOR_DIALOG_UPLOAD_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-chart'); 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'); static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals'); static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals-entry'); 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'); static final CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/import-chart'); static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide'); 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'); /** * 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 function openAboutDialog(state:ChartEditorState):Null { return openDialog(state, CHART_EDITOR_DIALOG_ABOUT_LAYOUT, true, true); } /** * 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):Null { var dialog:Null = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable); if (dialog == null) throw 'Could not locate Welcome dialog'; state.isHaxeUIDialogOpen = true; dialog.onDialogClosed = function(_event) { state.isHaxeUIDialogOpen = false; // Called when the Welcome dialog is closed while it is closable. state.stopWelcomeMusic(); } #if sys var splashRecentContainer:Null = dialog.findComponent('splashRecentContainer', VBox); if (splashRecentContainer == null) throw 'Could not locate splashRecentContainer in Welcome dialog'; for (chartPath in state.previousWorkingFilePaths) { if (chartPath == null) continue; var linkRecentChart:Link = new Link(); linkRecentChart.text = chartPath; linkRecentChart.onClick = function(_event) { dialog.hideDialog(DialogButton.CANCEL); state.stopWelcomeMusic(); // Load chart from file var result:Null> = ChartEditorImportExportHandler.loadFromFNFCPath(state, chartPath); if (result != null) { if (result.length == 0) { // No warnings. state.success('Loaded Chart', 'Loaded chart (${chartPath.toString()})'); } else { // One or more warnings. state.warning('Loaded Chart', 'Loaded chart (${chartPath.toString()})\n${result.join("\n")}'); } } else { state.error('Failed to Load Chart', 'Failed to load chart (${chartPath.toString()})'); } } if (!FileUtil.doesFileExist(chartPath)) { trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...'); linkRecentChart.disabled = true; } splashRecentContainer.addComponent(linkRecentChart); } #else var splashRecentContainer:Null = dialog.findComponent('splashRecentContainer', VBox); if (splashRecentContainer == null) throw 'Could not locate splashRecentContainer in Welcome dialog'; var webLoadLabel:Label = new Label(); webLoadLabel.text = 'Click the button below to load a chart file (.fnfc) from your computer.'; splashRecentContainer.add(webLoadLabel); #end // Create New Song "Easy/Normal/Hard" var linkCreateBasic:Null = dialog.findComponent('splashCreateFromSongBasicOnly', Link); if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasicOnly link in Welcome dialog'; linkCreateBasic.onClick = function(_event) { // Hide the welcome dialog dialog.hideDialog(DialogButton.CANCEL); state.stopWelcomeMusic(); // // Create Song Wizard // openCreateSongWizardBasicOnly(state, false); } // Create New Song "Erect/Nightmare" var linkCreateErect:Null = dialog.findComponent('splashCreateFromSongErectOnly', Link); if (linkCreateErect == null) throw 'Could not locate splashCreateFromSongErectOnly link in Welcome dialog'; linkCreateErect.onClick = function(_event) { // Hide the welcome dialog dialog.hideDialog(DialogButton.CANCEL); // // Create Song Wizard // openCreateSongWizardErectOnly(state, false); } // Create New Song "Easy/Normal/Hard/Erect/Nightmare" var linkCreateErect:Null = dialog.findComponent('splashCreateFromSongBasicErect', Link); if (linkCreateErect == null) throw 'Could not locate splashCreateFromSongBasicErect link in Welcome dialog'; linkCreateErect.onClick = function(_event) { // Hide the welcome dialog dialog.hideDialog(DialogButton.CANCEL); // // Create Song Wizard // openCreateSongWizardBasicErect(state, false); } var linkImportChartLegacy:Null = dialog.findComponent('splashImportChartLegacy', Link); if (linkImportChartLegacy == null) throw 'Could not locate splashImportChartLegacy link in Welcome dialog'; linkImportChartLegacy.onClick = function(_event) { // Hide the welcome dialog dialog.hideDialog(DialogButton.CANCEL); state.stopWelcomeMusic(); // Open the "Import Chart" dialog openImportChartWizard(state, 'legacy', false); }; var buttonBrowse:Null