diff --git a/source/funkin/play/song/formats/FNFLegacy.hx b/source/funkin/play/song/formats/FNFLegacy.hx new file mode 100644 index 000000000..51dc602f3 --- /dev/null +++ b/source/funkin/play/song/formats/FNFLegacy.hx @@ -0,0 +1,131 @@ +package funkin.play.song.formats; + +typedef FNFLegacy = +{ + var song:LegacySongData; +} + +typedef LegacySongData = +{ + var player1:String; // Boyfriend + var player2:String; // Opponent + + var speed:LegacyScrollSpeeds; + var stageDefault:String; + var bpm:Float; + var notes:LegacyNoteData; + var song:String; // Song name +}; + +typedef LegacyScrollSpeeds = +{ + var easy:Float; + var normal:Float; + var hard:Float; +}; + +typedef LegacyNoteData = +{ + /** + * The easy difficulty. + */ + var ?easy:Array; + + /** + * The normal difficulty. + */ + var ?normal:Array; + + /** + * The hard difficulty. + */ + var ?hard:Array; +}; + +typedef LegacyNoteSection = +{ + /** + * Whether the section is a must-hit section. + * If true, 0-3 are boyfriends notes, 4-7 are opponents notes. + * If false, 0-3 are opponents notes, 4-7 are boyfriends notes. + */ + var mustHitSection:Bool; + + /** + * Array of note data: + * - Direction + * - Time (ms) + * - Sustain Duration (ms) + * - Note kind (true = "alt", or string) + */ + var sectionNotes:Array; + + var typeOfSection:Int; + var lengthInSteps:Int; +} + +/** + * Notes in the old format are stored as an Array + */ +abstract LegacyNote(Array) +{ + public var time(get, set):Float; + + function get_time():Float + { + return this[0]; + } + + function set_time(value:Float):Float + { + return this[0] = value; + } + + public var data(get, set):Int; + + function get_data():Int + { + return this[1]; + } + + function set_data(value:Int):Int + { + return this[1] = value; + } + + public function getData(mustHitSection:Bool):Int + { + if (mustHitSection) return this[1]; + + return (this[1] + 4) % 8; + } + + public var length(get, set):Float; + + function get_length():Float + { + if (this.length < 3) return 0.0; + return this[2]; + } + + function set_length(value:Float):Float + { + return this[2] = value; + } + + public var kind(get, set):String; + + function get_kind():String + { + if (this.length < 4) return 'normal'; + + if (Std.isOfType(this[3], Bool)) return this[3] ? 'alt' : 'normal'; + + return this[3]; + } + + function set_kind(value:String):String + { + return this[3] = value; + } +} diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index 4240773e4..4044df5d8 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -1,13 +1,21 @@ package funkin.ui.debug.charting; +import funkin.play.character.CharacterData; +import funkin.util.Constants; +import funkin.util.SerializerUtil; +import funkin.play.song.SongData.SongChartData; +import funkin.play.song.SongData.SongMetadata; import flixel.util.FlxTimer; import funkin.input.Cursor; import funkin.play.character.BaseCharacter; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.song.Song; +import funkin.play.song.SongMigrator; +import funkin.play.song.SongValidator; import funkin.play.song.SongData.SongDataParser; import funkin.play.song.SongData.SongPlayableChar; import funkin.play.song.SongData.SongTimeChange; +import funkin.util.FileUtil; import haxe.io.Path; import haxe.ui.components.Button; import haxe.ui.components.DropDown; @@ -40,6 +48,9 @@ class ChartEditorDialogHandler static final CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata-chargroup'); 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_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart'); + static final CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/open-chart-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'); /** @@ -71,41 +82,32 @@ class ChartEditorDialogHandler // // Create Song Wizard // + openCreateSongWizard(state, false); + } - // Step 1. Upload Instrumental - var uploadInstDialog:Dialog = openUploadInstDialog(state, false); - uploadInstDialog.onDialogClosed = function(_event) { - state.isHaxeUIDialogOpen = false; - if (_event.button == DialogButton.APPLY) - { - // Step 2. Song Metadata - var songMetadataDialog:Dialog = openSongMetadataDialog(state); - songMetadataDialog.onDialogClosed = function(_event) { - state.isHaxeUIDialogOpen = false; - if (_event.button == DialogButton.APPLY) - { - // Step 3. Upload Vocals - // NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard. - openUploadVocalsDialog(state, false); // var uploadVocalsDialog:Dialog - } - else - { - // User cancelled the wizard! Back to the welcome dialog. - openWelcomeDialog(state); - } - }; - } - else - { - // User cancelled the wizard! Back to the welcome dialog. - openWelcomeDialog(state); - } - }; + var linkImportChartLegacy:Link = dialog.findComponent('splashImportChartLegacy', Link); + linkImportChartLegacy.onClick = function(_event) { + // Hide the welcome dialog + dialog.hideDialog(DialogButton.CANCEL); + + // Open the "Import Chart" dialog + openImportChartWizard(state, 'legacy', false); + }; + + var buttonBrowse:Button = dialog.findComponent('splashBrowse', Button); + buttonBrowse.onClick = function(_event) { + // Hide the welcome dialog + dialog.hideDialog(DialogButton.CANCEL); + + // Open the "Open Chart" dialog + openBrowseWizard(state, false); } var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox); var songList:Array = SongDataParser.listSongIds(); + // Sort alphabetically + songList.sort((a, b) -> a > b ? 1 : -1); for (targetSongId in songList) { @@ -130,6 +132,120 @@ class ChartEditorDialogHandler return dialog; } + /** + * 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); + openChartDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + if (_event.button == DialogButton.APPLY) + { + // Step 2. Upload instrumental + var uploadInstDialog:Dialog = openUploadInstDialog(state, closable); + uploadInstDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + if (_event.button == DialogButton.APPLY) + { + // 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 + uploadVocalsDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + state.postLoadInstrumental(); + } + } + else + { + // User cancelled the wizard! Back to the welcome dialog. + openWelcomeDialog(state); + } + }; + } + else + { + // User cancelled the wizard! Back to the welcome dialog. + openWelcomeDialog(state); + } + }; + } + + public static function openImportChartWizard(state:ChartEditorState, format:String, closable:Bool):Void + { + // Open the "Open Chart" wizard + // Step 1. Open Chart + var openChartDialog:Dialog = openImportChartDialog(state, format); + openChartDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + if (_event.button == DialogButton.APPLY) + { + // Step 2. Upload instrumental + var uploadInstDialog:Dialog = openUploadInstDialog(state, closable); + uploadInstDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + if (_event.button == DialogButton.APPLY) + { + // 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 + uploadVocalsDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + state.postLoadInstrumental(); + } + } + else + { + // User cancelled the wizard! Back to the welcome dialog. + openWelcomeDialog(state); + } + }; + } + else + { + // User cancelled the wizard! Back to the welcome dialog. + openWelcomeDialog(state); + } + }; + } + + public static function openCreateSongWizard(state:ChartEditorState, closable:Bool):Void + { + // Step 1. Upload Instrumental + var uploadInstDialog:Dialog = openUploadInstDialog(state, closable); + uploadInstDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + if (_event.button == DialogButton.APPLY) + { + // Step 2. Song Metadata + var songMetadataDialog:Dialog = openSongMetadataDialog(state); + songMetadataDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + if (_event.button == DialogButton.APPLY) + { + // Step 3. Upload Vocals + // NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard. + openUploadVocalsDialog(state, false); // var uploadVocalsDialog:Dialog + } + else + { + // User cancelled the wizard! Back to the welcome dialog. + openWelcomeDialog(state); + } + }; + } + else + { + // User cancelled the wizard! Back to the welcome dialog. + openWelcomeDialog(state); + } + }; + } + /** * Builds and opens a dialog where the user uploads an instrumental for the current song. * @param state The current chart editor state. @@ -214,11 +330,20 @@ class ChartEditorDialogHandler } else { + var message:String = if (!ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext)) + { + 'File format (${path.ext}) not supported for instrumental track (${path.file}.${path.ext})'; + } + else + { + 'Failed to load instrumental track (${path.file}.${path.ext})'; + } + // Tell the user the load was successful. NotificationManager.instance.addNotification( { title: 'Failure', - body: 'Failed to load instrumental track (${path.file}.${path.ext})', + body: message, type: NotificationType.Error, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); @@ -418,12 +543,6 @@ class ChartEditorDialogHandler moveCharGroup(event.data.id); }; - if (key == null) - { - // Find the next available player character. - trace(charGroupPlayer.dataSource.data); - } - var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown); charGroupOpponent.onChange = function(event:UIEvent) { charData.opponent = event.data.id; @@ -481,8 +600,8 @@ class ChartEditorDialogHandler for (charKey in charIdsForVocals) { trace('Adding vocal upload for character ${charKey}'); - var charMetadata:BaseCharacter = CharacterDataParser.fetchCharacter(charKey); - var charName:String = charMetadata.characterName; + var charMetadata:CharacterData = CharacterDataParser.fetchCharacterData(charKey); + var charName:String = charMetadata.name; var vocalsEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT); @@ -509,11 +628,20 @@ class ChartEditorDialogHandler } else { + var message:String = if (!ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext)) + { + 'File format (${path.ext}) not supported for vocal track (${path.file}.${path.ext})'; + } + else + { + 'Failed to load vocal track (${path.file}.${path.ext})'; + } + // Vocals failed to load. NotificationManager.instance.addNotification( { title: 'Failure', - body: 'Failed to load vocal track (${path.file}.${path.ext})', + body: message, type: NotificationType.Error, expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME }); @@ -550,9 +678,287 @@ class ChartEditorDialogHandler return dialog; } + /** + * 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') + public static function openChartDialog(state:ChartEditorState, ?closable:Bool = true):Dialog + { + var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_OPEN_CHART_LAYOUT, true, closable); + + var buttonCancel:Button = dialog.findComponent('dialogCancel', Button); + buttonCancel.onClick = function(_event) { + dialog.hideDialog(DialogButton.CANCEL); + } + + var chartContainerA:Component = dialog.findComponent('chartContainerA'); + var chartContainerB:Component = dialog.findComponent('chartContainerB'); + + var songMetadata:Map = []; + var songChartData:Map = []; + + var buttonContinue:Button = dialog.findComponent('dialogContinue', Button); + buttonContinue.onClick = function(_event) { + state.loadSong(songMetadata, songChartData); + + 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->Void = function(variations:Array) { + // Clear the chart container. + while (chartContainerB.getComponentAt(0) != null) + { + chartContainerB.removeComponent(chartContainerB.getComponentAt(0)); + } + + // Build an entry for -chart.json. + var songDefaultChartDataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT); + var songDefaultChartDataEntryLabel:Label = songDefaultChartDataEntry.findComponent('chartEntryLabel', Label); + songDefaultChartDataEntryLabel.text = 'Drag and drop -chart.json file, or click to browse.'; + + songDefaultChartDataEntry.onClick = onClickChartDataVariation.bind(Constants.DEFAULT_VARIATION).bind(songDefaultChartDataEntryLabel); + addDropHandler(songDefaultChartDataEntry, onDropFileChartDataVariation.bind(Constants.DEFAULT_VARIATION).bind(songDefaultChartDataEntryLabel)); + chartContainerB.addComponent(songDefaultChartDataEntry); + + for (variation in variations) + { + // Build entries for -metadata-.json. + var songVariationMetadataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT); + var songVariationMetadataEntryLabel:Label = songVariationMetadataEntry.findComponent('chartEntryLabel', Label); + songVariationMetadataEntryLabel.text = 'Drag and drop -metadata-${variation}.json file, or click to browse.'; + + songVariationMetadataEntry.onClick = onClickMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel); + addDropHandler(songVariationMetadataEntry, onDropFileMetadataVariation.bind(variation).bind(songVariationMetadataEntryLabel)); + chartContainerB.addComponent(songVariationMetadataEntry); + + // Build entries for -chart-.json. + var songVariationChartDataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT); + var songVariationChartDataEntryLabel:Label = songVariationChartDataEntry.findComponent('chartEntryLabel', Label); + songVariationChartDataEntryLabel.text = 'Drag and drop -chart-${variation}.json file, or click to browse.'; + + songVariationChartDataEntry.onClick = onClickChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel); + addDropHandler(songVariationChartDataEntry, onDropFileChartDataVariation.bind(variation).bind(songVariationChartDataEntryLabel)); + chartContainerB.addComponent(songVariationChartDataEntry); + } + } + + onDropFileMetadataVariation = function(variation:String, label:Label, pathStr:String) { + var path:Path = new Path(pathStr); + trace('Dropped JSON file (${path})'); + + var songMetadataJson:Dynamic = FileUtil.readJSONFromPath(path.toString()); + var songMetadataVariation:SongMetadata = SongMigrator.migrateSongMetadata(songMetadataJson, 'import'); + songMetadataVariation = SongValidator.validateSongMetadata(songMetadataVariation, 'import'); + + songMetadata.set(variation, songMetadataVariation); + + // Tell the user the load was successful. + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded metadata file (${path.file}.${path.ext})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + + label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}'; + + if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations); + }; + + onClickMetadataVariation = function(variation:String, label:Label, _event:UIEvent) { + Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [ + {label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) { + if (selectedFile != null) + { + trace('Selected file: ' + selectedFile.name); + + var songMetadataJson:Dynamic = SerializerUtil.fromJSONBytes(selectedFile.bytes); + var songMetadataVariation:SongMetadata = SongMigrator.migrateSongMetadata(songMetadataJson, 'import'); + songMetadataVariation = SongValidator.validateSongMetadata(songMetadataVariation, 'import'); + songMetadataVariation.variation = variation; + + songMetadata.set(variation, songMetadataVariation); + + // Tell the user the load was successful. + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded metadata file (${selectedFile.name})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + + label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}'; + + if (variation == Constants.DEFAULT_VARIATION) constructVariationEntries(songMetadataVariation.playData.songVariations); + } + }); + } + + onDropFileChartDataVariation = function(variation:String, label:Label, pathStr:String) { + var path:Path = new Path(pathStr); + trace('Dropped JSON file (${path})'); + + var songChartDataJson:Dynamic = FileUtil.readJSONFromPath(path.toString()); + var songChartDataVariation:SongChartData = SongMigrator.migrateSongChartData(songChartDataJson, 'import'); + songChartDataVariation = SongValidator.validateSongChartData(songChartDataVariation, 'import'); + + songChartData.set(variation, songChartDataVariation); + + // Tell the user the load was successful. + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded chart data file (${path.file}.${path.ext})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + + label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}'; + }; + + onClickChartDataVariation = function(variation:String, label:Label, _event:UIEvent) { + Dialogs.openBinaryFile('Open Chart ($variation) Metadata', [ + {label: 'JSON File (.json)', extension: 'json'}], function(selectedFile) { + if (selectedFile != null) + { + trace('Selected file: ' + selectedFile.name); + + var songChartDataJson:Dynamic = SerializerUtil.fromJSONBytes(selectedFile.bytes); + var songChartDataVariation:SongChartData = SongMigrator.migrateSongChartData(songChartDataJson, 'import'); + songChartDataVariation = SongValidator.validateSongChartData(songChartDataVariation, 'import'); + + songChartData.set(variation, songChartDataVariation); + + // Tell the user the load was successful. + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded chart data file (${selectedFile.name})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + + label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}'; + } + }); + } + + var metadataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_ENTRY_LAYOUT); + var metadataEntryLabel:Label = metadataEntry.findComponent('chartEntryLabel', Label); + metadataEntryLabel.text = 'Drag and drop -metadata.json file, or click to browse.'; + + metadataEntry.onClick = onClickMetadataVariation.bind(Constants.DEFAULT_VARIATION).bind(metadataEntryLabel); + addDropHandler(metadataEntry, onDropFileMetadataVariation.bind(Constants.DEFAULT_VARIATION).bind(metadataEntryLabel)); + + 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 + */ + public static function openImportChartDialog(state:ChartEditorState, format:String, ?closable:Bool = true):Dialog + { + var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_IMPORT_CHART_LAYOUT, true, closable); + + 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}'; + + var buttonCancel:Button = dialog.findComponent('dialogCancel', Button); + + buttonCancel.onClick = function(_event) { + dialog.hideDialog(DialogButton.CANCEL); + } + + var importBox:Box = dialog.findComponent('importBox', Box); + + importBox.onMouseOver = function(_event) { + importBox.swapClass('upload-bg', 'upload-bg-hover'); + Cursor.cursorMode = Pointer; + } + + importBox.onMouseOut = function(_event) { + importBox.swapClass('upload-bg-hover', 'upload-bg'); + Cursor.cursorMode = Default; + } + + var onDropFile:String->Void; + + importBox.onClick = function(_event) { + Dialogs.openBinaryFile('Import Chart - ${prettyFormat}', [fileFilter], function(selectedFile:SelectedFileInfo) { + if (selectedFile != null) + { + trace('Selected file: ' + selectedFile.fullPath); + var selectedFileJson:Dynamic = SerializerUtil.fromJSONBytes(selectedFile.bytes); + var songMetadata:SongMetadata = SongMigrator.migrateSongMetadataFromLegacy(selectedFileJson); + var songChartData:SongChartData = SongMigrator.migrateSongChartDataFromLegacy(selectedFileJson); + + state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]); + + dialog.hideDialog(DialogButton.APPLY); + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded chart file (${selectedFile.name})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + } + }); + } + + onDropFile = function(pathStr:String) { + var path:Path = new Path(pathStr); + var selectedFileJson:Dynamic = FileUtil.readJSONFromPath(path.toString()); + var songMetadata:SongMetadata = SongMigrator.migrateSongMetadataFromLegacy(selectedFileJson); + var songChartData:SongChartData = SongMigrator.migrateSongChartDataFromLegacy(selectedFileJson); + + state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]); + + dialog.hideDialog(DialogButton.APPLY); + NotificationManager.instance.addNotification( + { + title: 'Success', + body: 'Loaded chart file (${path.file}.${path.ext})', + type: NotificationType.Success, + expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME + }); + }; + + addDropHandler(importBox, onDropFile); + + return dialog; + } + /** * Builds and opens a dialog displaying the user guide, providing guidance and help on how to use the chart editor. - * + * * @param state The current chart editor state. * @return The dialog that was opened. */ @@ -569,6 +975,8 @@ class ChartEditorDialogHandler static function openDialog(state:ChartEditorState, key:String, modal:Bool = true, closable:Bool = true):Dialog { var dialog:Dialog = cast state.buildComponent(key); + if (dialog == null) return null; + dialog.destroyOnClose = true; dialog.closable = closable; dialog.showDialog(modal); diff --git a/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx index 323d11abd..2016e4ccc 100644 --- a/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx @@ -27,7 +27,7 @@ class ChartEditorEventSprite extends FlxSprite /** * The image used for all song events. Cached for performance. */ - var eventGraphic:BitmapData; + static var eventSpriteBasic:BitmapData; public function new(parent:ChartEditorState) { @@ -40,12 +40,12 @@ class ChartEditorEventSprite extends FlxSprite function buildGraphic():Void { - if (eventGraphic == null) + if (eventSpriteBasic == null) { - eventGraphic = Assets.getBitmapData(Paths.image('ui/chart-editor/event')); + eventSpriteBasic = Assets.getBitmapData(Paths.image('ui/chart-editor/event')); } - loadGraphic(eventGraphic); + loadGraphic(eventSpriteBasic); setGraphicSize(ChartEditorState.GRID_SIZE); this.updateHitbox(); } diff --git a/source/funkin/ui/debug/charting/ChartEditorThemeHandler.hx b/source/funkin/ui/debug/charting/ChartEditorThemeHandler.hx index 7bdf366bf..40c797169 100644 --- a/source/funkin/ui/debug/charting/ChartEditorThemeHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorThemeHandler.hx @@ -26,7 +26,7 @@ class ChartEditorThemeHandler // An enum of typedefs or something? // ================================ static final BACKGROUND_COLOR_LIGHT:FlxColor = 0xFF673AB7; - static final BACKGROUND_COLOR_DARK:FlxColor = 0xFF673AB7; + static final BACKGROUND_COLOR_DARK:FlxColor = 0xFF361E60; // Color 1 of the grid pattern. Alternates with Color 2. static final GRID_COLOR_1_LIGHT:FlxColor = 0xFFE7E6E6; @@ -43,13 +43,11 @@ class ChartEditorThemeHandler // Vertical divider between characters. static final GRID_STRUMLINE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111; static final GRID_STRUMLINE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4; - // static final GRID_STRUMLINE_DIVIDER_WIDTH:Float = 2; static final GRID_STRUMLINE_DIVIDER_WIDTH:Float = ChartEditorState.GRID_SELECTION_BORDER_WIDTH; // Horizontal divider between measures. static final GRID_MEASURE_DIVIDER_COLOR_LIGHT:FlxColor = 0xFF111111; static final GRID_MEASURE_DIVIDER_COLOR_DARK:FlxColor = 0xFFC4C4C4; - // static final GRID_MEASURE_DIVIDER_WIDTH:Float = 2; static final GRID_MEASURE_DIVIDER_WIDTH:Float = ChartEditorState.GRID_SELECTION_BORDER_WIDTH; // Border on the square highlighting selected notes. @@ -66,6 +64,12 @@ class ChartEditorThemeHandler static final PLAYHEAD_BLOCK_BORDER_COLOR:FlxColor = 0xFF9D0011; static final PLAYHEAD_BLOCK_FILL_COLOR:FlxColor = 0xFFBD0231; + static final TOTAL_COLUMN_COUNT:Int = ChartEditorState.STRUMLINE_SIZE * 2 + 1; + + /** + * When the theme is changed, this function updates all of the UI elements to match the new theme. + * @param state The ChartEditorState to update. + */ public static function updateTheme(state:ChartEditorState):Void { updateBackground(state); @@ -73,6 +77,10 @@ class ChartEditorThemeHandler updateSelectionSquare(state); } + /** + * Updates the tint of the background sprite to match the current theme. + * @param state The ChartEditorState to update. + */ static function updateBackground(state:ChartEditorState):Void { state.menuBG.color = switch (state.currentTheme) @@ -85,7 +93,7 @@ class ChartEditorThemeHandler /** * Builds the checkerboard background image of the chart editor, and adds dividing lines to it. - * @param dark Whether to draw the grid in a dark color instead of a light one. + * @param state The ChartEditorState to update. */ static function updateGridBitmap(state:ChartEditorState):Void { @@ -107,8 +115,8 @@ class ChartEditorThemeHandler // 2 * (Strumline Size) + 1 grid squares wide, by (4 * quarter notes per measure) grid squares tall. // This gets reused to fill the screen. - var gridWidth:Int = Std.int(ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2 + 1)); - var gridHeight:Int = Std.int(ChartEditorState.GRID_SIZE * (Conductor.stepsPerMeasure)); + var gridWidth:Int = Std.int(ChartEditorState.GRID_SIZE * TOTAL_COLUMN_COUNT); + var gridHeight:Int = Std.int(ChartEditorState.GRID_SIZE * Conductor.stepsPerMeasure); state.gridBitmap = FlxGridOverlay.createGrid(ChartEditorState.GRID_SIZE, ChartEditorState.GRID_SIZE, gridWidth, gridHeight, true, gridColor1, gridColor2); // Selection borders @@ -143,7 +151,7 @@ class ChartEditorThemeHandler selectionBorderColor); // Selection borders across the middle. - for (i in 1...(ChartEditorState.STRUMLINE_SIZE * 2 + 1)) + for (i in 1...TOTAL_COLUMN_COUNT) { state.gridBitmap.fillRect(new Rectangle((ChartEditorState.GRID_SIZE * i) - (ChartEditorState.GRID_SELECTION_BORDER_WIDTH / 2), 0, ChartEditorState.GRID_SELECTION_BORDER_WIDTH, state.gridBitmap.height), @@ -167,7 +175,7 @@ class ChartEditorThemeHandler // Divider at top state.gridBitmap.fillRect(new Rectangle(0, 0, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor); // Divider at bottom - var dividerLineBY = state.gridBitmap.height - (GRID_MEASURE_DIVIDER_WIDTH / 2); + var dividerLineBY:Float = state.gridBitmap.height - (GRID_MEASURE_DIVIDER_WIDTH / 2); state.gridBitmap.fillRect(new Rectangle(0, dividerLineBY, state.gridBitmap.width, GRID_MEASURE_DIVIDER_WIDTH / 2), gridMeasureDividerColor); // Draw dividers between the strumlines. @@ -180,10 +188,10 @@ class ChartEditorThemeHandler }; // Divider at 1 * (Strumline Size) - var dividerLineAX = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2); + var dividerLineAX:Float = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2); state.gridBitmap.fillRect(new Rectangle(dividerLineAX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor); // Divider at 2 * (Strumline Size) - var dividerLineBX = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2); + var dividerLineBX:Float = ChartEditorState.GRID_SIZE * (ChartEditorState.STRUMLINE_SIZE * 2) - (GRID_STRUMLINE_DIVIDER_WIDTH / 2); state.gridBitmap.fillRect(new Rectangle(dividerLineBX, 0, GRID_STRUMLINE_DIVIDER_WIDTH, state.gridBitmap.height), gridStrumlineDividerColor); if (state.gridTiledSprite != null) diff --git a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx index 5a903481e..d849fa894 100644 --- a/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorToolboxHandler.hx @@ -1,6 +1,5 @@ package funkin.ui.debug.charting; -import haxe.ui.data.ArrayDataSource; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.event.SongEvent; import funkin.play.event.SongEventData; @@ -12,15 +11,17 @@ import haxe.ui.components.CheckBox; import haxe.ui.components.DropDown; import haxe.ui.components.Label; import haxe.ui.components.NumberStepper; -import haxe.ui.components.NumberStepper; import haxe.ui.components.Slider; import haxe.ui.components.TextField; -import haxe.ui.containers.dialogs.Dialog; import haxe.ui.containers.Box; -import haxe.ui.containers.Frame; import haxe.ui.containers.Grid; import haxe.ui.containers.Group; +import haxe.ui.containers.VBox; +import haxe.ui.containers.dialogs.CollapsibleDialog; +import haxe.ui.containers.dialogs.Dialog.DialogButton; +import haxe.ui.containers.dialogs.Dialog.DialogEvent; import haxe.ui.core.Component; +import haxe.ui.data.ArrayDataSource; import haxe.ui.events.UIEvent; /** @@ -32,18 +33,26 @@ enum ChartEditorToolMode Place; } +/** + * Static functions which handle building themed UI elements for a provided ChartEditorState. + */ class ChartEditorToolboxHandler { public static function setToolboxState(state:ChartEditorState, id:String, shown:Bool):Void { - if (shown) showToolbox(state, id); + if (shown) + { + showToolbox(state, id); + } else + { hideToolbox(state, id); + } } - public static function showToolbox(state:ChartEditorState, id:String) + public static function showToolbox(state:ChartEditorState, id:String):Void { - var toolbox:Dialog = state.activeToolboxes.get(id); + var toolbox:CollapsibleDialog = state.activeToolboxes.get(id); if (toolbox == null) toolbox = initToolbox(state, id); @@ -59,7 +68,7 @@ class ChartEditorToolboxHandler public static function hideToolbox(state:ChartEditorState, id:String):Void { - var toolbox:Dialog = state.activeToolboxes.get(id); + var toolbox:CollapsibleDialog = state.activeToolboxes.get(id); if (toolbox == null) toolbox = initToolbox(state, id); @@ -73,13 +82,27 @@ class ChartEditorToolboxHandler } } - public static function minimizeToolbox(state:ChartEditorState, id:String):Void {} - - public static function maximizeToolbox(state:ChartEditorState, id:String):Void {} - - public static function initToolbox(state:ChartEditorState, id:String):Dialog + public static function minimizeToolbox(state:ChartEditorState, id:String):Void { - var toolbox:Dialog = null; + var toolbox:CollapsibleDialog = state.activeToolboxes.get(id); + + if (toolbox == null) return; + + toolbox.minimized = true; + } + + public static function maximizeToolbox(state:ChartEditorState, id:String):Void + { + var toolbox:CollapsibleDialog = state.activeToolboxes.get(id); + + if (toolbox == null) return; + + toolbox.minimized = false; + } + + public static function initToolbox(state:ChartEditorState, id:String):CollapsibleDialog + { + var toolbox:CollapsibleDialog = null; switch (id) { case ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT: @@ -95,9 +118,9 @@ class ChartEditorToolboxHandler case ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT: toolbox = buildToolboxCharactersLayout(state); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT: - toolbox = buildToolboxPlayerPreviewLayout(state); + toolbox = null; // buildToolboxPlayerPreviewLayout(state); case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT: - toolbox = buildToolboxOpponentPreviewLayout(state); + toolbox = null; // buildToolboxOpponentPreviewLayout(state); default: // This happens if you try to load an unknown layout. trace('ChartEditorToolboxHandler.initToolbox() - Unknown toolbox ID: $id'); @@ -114,9 +137,15 @@ class ChartEditorToolboxHandler return toolbox; } - public static function getToolbox(state:ChartEditorState, id:String):Dialog + /** + * Retrieve a toolbox by its layout's asset ID. + * @param state The ChartEditorState instance. + * @param id The asset ID of the toolbox layout. + * @return The toolbox. + */ + public static function getToolbox(state:ChartEditorState, id:String):CollapsibleDialog { - var toolbox:Dialog = state.activeToolboxes.get(id); + var toolbox:CollapsibleDialog = state.activeToolboxes.get(id); // Initialize the toolbox without showing it. if (toolbox == null) toolbox = initToolbox(state, id); @@ -124,9 +153,9 @@ class ChartEditorToolboxHandler return toolbox; } - static function buildToolboxToolsLayout(state:ChartEditorState):Dialog + static function buildToolboxToolsLayout(state:ChartEditorState):CollapsibleDialog { - var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT); + var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_TOOLS_LAYOUT); if (toolbox == null) return null; @@ -134,15 +163,15 @@ class ChartEditorToolboxHandler toolbox.x = 50; toolbox.y = 50; - toolbox.onDialogClosed = (event:DialogEvent) -> { + toolbox.onDialogClosed = function(event:DialogEvent) { state.setUICheckboxSelected('menubarItemToggleToolboxTools', false); } - var toolsGroup:Group = toolbox.findComponent("toolboxToolsGroup", Group); + var toolsGroup:Group = toolbox.findComponent('toolboxToolsGroup', Group); if (toolsGroup == null) return null; - toolsGroup.onChange = (event:UIEvent) -> { + toolsGroup.onChange = function(event:UIEvent) { switch (event.target.id) { case 'toolboxToolsGroupSelect': @@ -157,9 +186,9 @@ class ChartEditorToolboxHandler return toolbox; } - static function buildToolboxNoteDataLayout(state:ChartEditorState):Dialog + static function buildToolboxNoteDataLayout(state:ChartEditorState):CollapsibleDialog { - var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT); + var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT); if (toolbox == null) return null; @@ -167,16 +196,16 @@ class ChartEditorToolboxHandler toolbox.x = 75; toolbox.y = 100; - toolbox.onDialogClosed = (event:DialogEvent) -> { + toolbox.onDialogClosed = function(event:DialogEvent) { state.setUICheckboxSelected('menubarItemToggleToolboxNotes', false); } - var toolboxNotesNoteKind:DropDown = toolbox.findComponent("toolboxNotesNoteKind", DropDown); - var toolboxNotesCustomKindLabel:Label = toolbox.findComponent("toolboxNotesCustomKindLabel", Label); - var toolboxNotesCustomKind:TextField = toolbox.findComponent("toolboxNotesCustomKind", TextField); + var toolboxNotesNoteKind:DropDown = toolbox.findComponent('toolboxNotesNoteKind', DropDown); + var toolboxNotesCustomKindLabel:Label = toolbox.findComponent('toolboxNotesCustomKindLabel', Label); + var toolboxNotesCustomKind:TextField = toolbox.findComponent('toolboxNotesCustomKind', TextField); - toolboxNotesNoteKind.onChange = (event:UIEvent) -> { - var isCustom = (event.data.id == '~CUSTOM~'); + toolboxNotesNoteKind.onChange = function(event:UIEvent) { + var isCustom:Bool = (event.data.id == '~CUSTOM~'); if (isCustom) { @@ -194,16 +223,16 @@ class ChartEditorToolboxHandler } } - toolboxNotesCustomKind.onChange = (event:UIEvent) -> { + toolboxNotesCustomKind.onChange = function(event:UIEvent) { state.selectedNoteKind = toolboxNotesCustomKind.text; } return toolbox; } - static function buildToolboxEventDataLayout(state:ChartEditorState):Dialog + static function buildToolboxEventDataLayout(state:ChartEditorState):CollapsibleDialog { - var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT); + var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT); if (toolbox == null) return null; @@ -211,12 +240,12 @@ class ChartEditorToolboxHandler toolbox.x = 100; toolbox.y = 150; - toolbox.onDialogClosed = (event:DialogEvent) -> { + toolbox.onDialogClosed = function(event:DialogEvent) { state.setUICheckboxSelected('menubarItemToggleToolboxEvents', false); } - var toolboxEventsEventKind:DropDown = toolbox.findComponent("toolboxEventsEventKind", DropDown); - var toolboxEventsDataGrid:Grid = toolbox.findComponent("toolboxEventsDataGrid", Grid); + var toolboxEventsEventKind:DropDown = toolbox.findComponent('toolboxEventsEventKind', DropDown); + var toolboxEventsDataGrid:Grid = toolbox.findComponent('toolboxEventsDataGrid', Grid); toolboxEventsEventKind.dataSource = new ArrayDataSource(); @@ -227,7 +256,7 @@ class ChartEditorToolboxHandler toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id}); } - toolboxEventsEventKind.onChange = (event:UIEvent) -> { + toolboxEventsEventKind.onChange = function(event:UIEvent) { var eventType:String = event.data.value; trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType'); @@ -281,9 +310,9 @@ class ChartEditorToolboxHandler numberStepper.value = field.defaultValue; input = numberStepper; case BOOL: - var checkBox = new CheckBox(); + var checkBox:CheckBox = new CheckBox(); checkBox.id = field.name; - checkBox.selected = field.defaultValue == true; + checkBox.selected = field.defaultValue; input = checkBox; case ENUM: var dropDown:DropDown = new DropDown(); @@ -293,7 +322,7 @@ class ChartEditorToolboxHandler // Add entries to the dropdown. for (optionName in field.keys.keys()) { - var optionValue = field.keys.get(optionName); + var optionValue:String = field.keys.get(optionName); trace('$optionName : $optionValue'); dropDown.dataSource.add({value: optionValue, text: optionName}); } @@ -314,7 +343,7 @@ class ChartEditorToolboxHandler target.addComponent(input); - input.onChange = (event:UIEvent) -> { + input.onChange = function(event:UIEvent) { trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${event.target.value}'); if (event.target.value == null) state.selectedEventData.remove(event.target.id); @@ -324,9 +353,9 @@ class ChartEditorToolboxHandler } } - static function buildToolboxDifficultyLayout(state:ChartEditorState):Dialog + static function buildToolboxDifficultyLayout(state:ChartEditorState):CollapsibleDialog { - var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); + var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); if (toolbox == null) return null; @@ -334,36 +363,36 @@ class ChartEditorToolboxHandler toolbox.x = 125; toolbox.y = 200; - toolbox.onDialogClosed = (event:DialogEvent) -> { + toolbox.onDialogClosed = function(event:UIEvent) { state.setUICheckboxSelected('menubarItemToggleToolboxDifficulty', false); } - var difficultyToolboxSaveMetadata:Button = toolbox.findComponent("difficultyToolboxSaveMetadata", Button); - var difficultyToolboxSaveChart:Button = toolbox.findComponent("difficultyToolboxSaveChart", Button); - var difficultyToolboxSaveAll:Button = toolbox.findComponent("difficultyToolboxSaveAll", Button); - var difficultyToolboxLoadMetadata:Button = toolbox.findComponent("difficultyToolboxLoadMetadata", Button); - var difficultyToolboxLoadChart:Button = toolbox.findComponent("difficultyToolboxLoadChart", Button); + var difficultyToolboxSaveMetadata:Button = toolbox.findComponent('difficultyToolboxSaveMetadata', Button); + var difficultyToolboxSaveChart:Button = toolbox.findComponent('difficultyToolboxSaveChart', Button); + var difficultyToolboxSaveAll:Button = toolbox.findComponent('difficultyToolboxSaveAll', Button); + var difficultyToolboxLoadMetadata:Button = toolbox.findComponent('difficultyToolboxLoadMetadata', Button); + var difficultyToolboxLoadChart:Button = toolbox.findComponent('difficultyToolboxLoadChart', Button); - difficultyToolboxSaveMetadata.onClick = (event:UIEvent) -> { + difficultyToolboxSaveMetadata.onClick = function(event:UIEvent) { SongSerializer.exportSongMetadata(state.currentSongMetadata); }; - difficultyToolboxSaveChart.onClick = (event:UIEvent) -> { + difficultyToolboxSaveChart.onClick = function(event:UIEvent) { SongSerializer.exportSongChartData(state.currentSongChartData); }; - difficultyToolboxSaveAll.onClick = (event:UIEvent) -> { + difficultyToolboxSaveAll.onClick = function(event:UIEvent) { state.exportAllSongData(); }; - difficultyToolboxLoadMetadata.onClick = (event:UIEvent) -> { + difficultyToolboxLoadMetadata.onClick = function(event:UIEvent) { // Replace metadata for current variation. SongSerializer.importSongMetadataAsync(function(songMetadata) { state.currentSongMetadata = songMetadata; }); }; - difficultyToolboxLoadChart.onClick = (event:UIEvent) -> { + difficultyToolboxLoadChart.onClick = function(event:UIEvent) { // Replace chart data for current variation. SongSerializer.importSongChartDataAsync(function(songChartData) { state.currentSongChartData = songChartData; @@ -376,9 +405,9 @@ class ChartEditorToolboxHandler return toolbox; } - static function buildToolboxMetadataLayout(state:ChartEditorState):Dialog + static function buildToolboxMetadataLayout(state:ChartEditorState):CollapsibleDialog { - var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); + var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); if (toolbox == null) return null; @@ -386,13 +415,13 @@ class ChartEditorToolboxHandler toolbox.x = 150; toolbox.y = 250; - toolbox.onDialogClosed = (event:DialogEvent) -> { + toolbox.onDialogClosed = function(event:UIEvent) { state.setUICheckboxSelected('menubarItemToggleToolboxMetadata', false); } var inputSongName:TextField = toolbox.findComponent('inputSongName', TextField); - inputSongName.onChange = (event:UIEvent) -> { - var valid = event.target.text != null && event.target.text != ""; + inputSongName.onChange = function(event:UIEvent) { + var valid:Bool = event.target.text != null && event.target.text != ''; if (valid) { @@ -404,10 +433,11 @@ class ChartEditorToolboxHandler state.currentSongMetadata.songName = null; } }; + inputSongName.value = state.currentSongMetadata.songName; var inputSongArtist:TextField = toolbox.findComponent('inputSongArtist', TextField); - inputSongArtist.onChange = (event:UIEvent) -> { - var valid = event.target.text != null && event.target.text != ""; + inputSongArtist.onChange = function(event:UIEvent) { + var valid:Bool = event.target.text != null && event.target.text != ''; if (valid) { @@ -419,28 +449,31 @@ class ChartEditorToolboxHandler state.currentSongMetadata.artist = null; } }; + inputSongArtist.value = state.currentSongMetadata.artist; var inputStage:DropDown = toolbox.findComponent('inputStage', DropDown); - inputStage.onChange = (event:UIEvent) -> { - var valid = event.data != null && event.data.id != null; + inputStage.onChange = function(event:UIEvent) { + var valid:Bool = event.data != null && event.data.id != null; if (valid) { state.currentSongMetadata.playData.stage = event.data.id; } }; + inputStage.value = state.currentSongMetadata.playData.stage; var inputNoteSkin:DropDown = toolbox.findComponent('inputNoteSkin', DropDown); - inputNoteSkin.onChange = (event:UIEvent) -> { + inputNoteSkin.onChange = function(event:UIEvent) { if (event.data.id == null) return; state.currentSongMetadata.playData.noteSkin = event.data.id; }; + inputNoteSkin.value = state.currentSongMetadata.playData.noteSkin; var inputBPM:NumberStepper = toolbox.findComponent('inputBPM', NumberStepper); - inputBPM.onChange = (event:UIEvent) -> { + inputBPM.onChange = function(event:UIEvent) { if (event.value == null || event.value <= 0) return; - var timeChanges = state.currentSongMetadata.timeChanges; + var timeChanges:Array = state.currentSongMetadata.timeChanges; if (timeChanges == null || timeChanges.length == 0) { timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])]; @@ -454,28 +487,30 @@ class ChartEditorToolboxHandler state.currentSongMetadata.timeChanges = timeChanges; }; + inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm; var inputScrollSpeed:Slider = toolbox.findComponent('inputScrollSpeed', Slider); - inputScrollSpeed.onChange = (event:UIEvent) -> { - var valid = event.target.value != null && event.target.value > 0; + inputScrollSpeed.onChange = function(event:UIEvent) { + var valid:Bool = event.target.value != null && event.target.value > 0; if (valid) { inputScrollSpeed.removeClass('invalid-value'); - state.currentSongChartData.scrollSpeed = event.target.value; + state.currentSongChartScrollSpeed = event.target.value; } else { - state.currentSongChartData.scrollSpeed = null; + state.currentSongChartScrollSpeed = 1.0; } }; + inputScrollSpeed.value = state.currentSongChartData.scrollSpeed; return toolbox; } - static function buildToolboxCharactersLayout(state:ChartEditorState):Dialog + static function buildToolboxCharactersLayout(state:ChartEditorState):CollapsibleDialog { - var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT); + var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_CHARACTERS_LAYOUT); if (toolbox == null) return null; @@ -483,16 +518,16 @@ class ChartEditorToolboxHandler toolbox.x = 175; toolbox.y = 300; - toolbox.onDialogClosed = (event:DialogEvent) -> { + toolbox.onDialogClosed = function(event:DialogEvent) { state.setUICheckboxSelected('menubarItemToggleToolboxCharacters', false); } return toolbox; } - static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Dialog + static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):CollapsibleDialog { - var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT); + var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT); if (toolbox == null) return null; @@ -500,23 +535,23 @@ class ChartEditorToolboxHandler toolbox.x = 200; toolbox.y = 350; - toolbox.onDialogClosed = (event:DialogEvent) -> { + toolbox.onDialogClosed = function(event:DialogEvent) { state.setUICheckboxSelected('menubarItemToggleToolboxPlayerPreview', false); } var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer'); // TODO: We need to implement character swapping in ChartEditorState. charPlayer.loadCharacter('bf'); - // charPlayer.setScale(0.5); - charPlayer.setCharacterType(CharacterType.BF); + charPlayer.characterType = CharacterType.BF; charPlayer.flip = true; + charPlayer.targetScale = 0.5; return toolbox; } - static function buildToolboxOpponentPreviewLayout(state:ChartEditorState):Dialog + static function buildToolboxOpponentPreviewLayout(state:ChartEditorState):CollapsibleDialog { - var toolbox:Dialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT); + var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT); if (toolbox == null) return null; @@ -524,16 +559,18 @@ class ChartEditorToolboxHandler toolbox.x = 200; toolbox.y = 350; - toolbox.onDialogClosed = (event:DialogEvent) -> { + var container:VBox = toolbox.findComponent('charPlayerContainer', VBox); + + toolbox.onDialogClosed = function(event:DialogEvent) { state.setUICheckboxSelected('menubarItemToggleToolboxOpponentPreview', false); } var charPlayer:CharacterPlayer = toolbox.findComponent('charPlayer'); // TODO: We need to implement character swapping in ChartEditorState. charPlayer.loadCharacter('dad'); - // charPlayer.setScale(0.5); - charPlayer.setCharacterType(CharacterType.DAD); + charPlayer.characterType = CharacterType.DAD; charPlayer.flip = false; + charPlayer.targetScale = 0.5; return toolbox; }