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.data.stage.StageData; import funkin.ui.debug.charting.dialogs.ChartEditorAboutDialog; import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget; import funkin.ui.debug.charting.dialogs.ChartEditorCharacterIconSelectorMenu; import funkin.ui.debug.charting.dialogs.ChartEditorUploadChartDialog; import funkin.ui.debug.charting.dialogs.ChartEditorWelcomeDialog; import funkin.ui.debug.charting.dialogs.ChartEditorUploadVocalsDialog; import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.util.Constants; import funkin.util.DateUtil; import funkin.util.FileUtil; import funkin.util.SerializerUtil; import funkin.util.SortUtil; import funkin.util.VersionUtil; import funkin.util.WindowUtil; 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.menus.Menu; import haxe.ui.containers.VBox; import haxe.ui.core.Component; import haxe.ui.events.UIEvent; import haxe.ui.notifications.NotificationManager; import haxe.ui.notifications.NotificationType; import haxe.ui.RuntimeComponentBuilder; 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_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_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'); static final CHART_EDITOR_DIALOG_BACKUP_AVAILABLE_LAYOUT:String = Paths.ui('chart-editor/dialogs/backup-available'); /** * 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 { var dialog = ChartEditorAboutDialog.build(state); dialog.zIndex = 1000; state.isHaxeUIDialogOpen = true; return dialog; } /** * 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 = ChartEditorWelcomeDialog.build(state, closable); dialog.zIndex = 1000; state.isHaxeUIDialogOpen = true; state.fadeInWelcomeMusic(); return dialog; } /** * 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 { 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 = [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 { var menu = ChartEditorCharacterIconSelectorMenu.build(state, charType, lockPosition); menu.zIndex = 1000; return menu; } /** * Builds and opens a dialog letting the user know a backup is available, and prompting them to load it. */ public static function openBackupAvailableDialog(state:ChartEditorState, welcomeDialog:Null):Null { var dialog:Null = openDialog(state, CHART_EDITOR_DIALOG_BACKUP_AVAILABLE_LAYOUT, true, true); if (dialog == null) throw 'Could not locate Backup Available dialog'; dialog.onDialogClosed = function(event) { state.isHaxeUIDialogOpen = false; if (event.button == DialogButton.APPLY) { // User loaded the backup! Close the welcome dialog behind this. if (welcomeDialog != null) welcomeDialog.hideDialog(DialogButton.APPLY); } else { // User cancelled the dialog, don't close the welcome dialog so we aren't in a broken state. } }; state.isHaxeUIDialogOpen = true; var backupTimeLabel:Null