package funkin.ui.debug.charting.toolboxes; import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.CharacterData; import funkin.data.stage.StageData; import funkin.data.stage.StageRegistry; import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand; import funkin.ui.debug.charting.util.ChartEditorDropdowns; import haxe.ui.components.Button; import haxe.ui.components.CheckBox; import haxe.ui.containers.dialogs.Dialogs; import haxe.ui.containers.dialogs.Dialog.DialogButton; import funkin.data.song.SongData.SongMetadata; import haxe.ui.components.DropDown; import haxe.ui.components.HorizontalSlider; import funkin.util.FileUtil; import haxe.ui.containers.dialogs.MessageBox.MessageBoxType; import funkin.play.song.SongSerializer; import haxe.ui.components.Label; import haxe.ui.components.NumberStepper; import haxe.ui.components.Slider; import haxe.ui.components.TextField; import funkin.play.stage.Stage; import haxe.ui.containers.Box; import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeViewNode; import haxe.ui.containers.Frame; import haxe.ui.events.UIEvent; /** * The toolbox which allows viewing the list of difficulties, switching to a specific one, * and adding/removing variations and difficulties. */ // @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros. @:access(funkin.ui.debug.charting.ChartEditorState) @:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/difficulty.xml")) class ChartEditorDifficultyToolbox extends ChartEditorBaseToolbox { var difficultyToolboxTree:TreeView; var difficultyToolboxAddVariation:Button; var difficultyToolboxAddDifficulty:Button; var difficultyToolboxRemoveDifficulty:Button; var difficultyToolboxSaveMetadata:Button; var difficultyToolboxSaveChart:Button; var difficultyToolboxLoadMetadata:Button; var difficultyToolboxLoadChart:Button; public function new(chartEditorState2:ChartEditorState) { super(chartEditorState2); initialize(); this.onDialogClosed = onClose; } function onClose(event:UIEvent) { chartEditorState.menubarItemToggleToolboxDifficulty.selected = false; } function initialize():Void { // Starting position. // TODO: Save and load this. this.x = 150; this.y = 250; difficultyToolboxAddVariation.onClick = function(_:UIEvent) { chartEditorState.openAddVariationDialog(true); }; difficultyToolboxAddDifficulty.onClick = function(_:UIEvent) { chartEditorState.openAddDifficultyDialog(true); }; difficultyToolboxRemoveDifficulty.onClick = function(_:UIEvent) { var currentVariation:String = chartEditorState.selectedVariation; var currentDifficulty:String = chartEditorState.selectedDifficulty; trace('Removing difficulty "$currentVariation:$currentDifficulty"'); var callback = (button) -> { switch (button) { case DialogButton.YES: // Remove the difficulty. chartEditorState.removeDifficulty(currentVariation, currentDifficulty); refresh(); case DialogButton.NO: // Do nothing. default: // Do nothing. } } Dialogs.messageBox("Are you sure? This cannot be undone.", "Remove Difficulty", MessageBoxType.TYPE_YESNO, callback); }; difficultyToolboxSaveMetadata.onClick = function(_:UIEvent) { var vari:String = chartEditorState.selectedVariation != Constants.DEFAULT_VARIATION ? '-${chartEditorState.selectedVariation}' : ''; FileUtil.writeFileReference('${chartEditorState.currentSongId}$vari-metadata.json', chartEditorState.currentSongMetadata.serialize()); }; difficultyToolboxSaveChart.onClick = function(_:UIEvent) { var vari:String = chartEditorState.selectedVariation != Constants.DEFAULT_VARIATION ? '-${chartEditorState.selectedVariation}' : ''; FileUtil.writeFileReference('${chartEditorState.currentSongId}$vari-chart.json', chartEditorState.currentSongChartData.serialize()); }; difficultyToolboxLoadMetadata.onClick = function(_:UIEvent) { // Replace metadata for current variation. SongSerializer.importSongMetadataAsync(function(songMetadata) { chartEditorState.currentSongMetadata = songMetadata; }); }; difficultyToolboxLoadChart.onClick = function(_:UIEvent) { // Replace chart data for current variation. SongSerializer.importSongChartDataAsync(function(songChartData) { chartEditorState.currentSongChartData = songChartData; chartEditorState.noteDisplayDirty = true; }); }; refresh(); } /** * Clear the tree view and rebuild it with the current song metadata (variation and difficulty list). */ public function updateTree():Void { // Clear the tree view so we can rebuild it. difficultyToolboxTree.clearNodes(); // , icon: 'haxeui-core/styles/default/haxeui_tiny.png' var treeSong:TreeViewNode = difficultyToolboxTree.addNode({id: 'stv_song', text: 'S: ${chartEditorState.currentSongName}'}); treeSong.expanded = true; for (curVariation in chartEditorState.availableVariations) { var variationMetadata:Null = chartEditorState.songMetadata.get(curVariation); if (variationMetadata == null) continue; var treeVariation:TreeViewNode = treeSong.addNode( { id: 'stv_variation_$curVariation', text: 'V: ${curVariation.toTitleCase()}' }); treeVariation.expanded = true; var difficultyList:Array = variationMetadata.playData.difficulties; for (difficulty in difficultyList) { var _treeDifficulty:TreeViewNode = treeVariation.addNode( { id: 'stv_difficulty_${curVariation}_$difficulty', text: 'D: ${difficulty.toTitleCase()}' }); } } difficultyToolboxTree.onChange = onTreeChange; refreshTreeSelection(); } /** * Set the selected item in the tree to the current variation/difficulty. * * @param targetNode The node to select. If null, the current variation/difficulty will be used. */ public function refreshTreeSelection():Void { var targetNode = getCurrentTreeNode(); if (targetNode != null) difficultyToolboxTree.selectedNode = targetNode; } /** * Get the node in the tree representing the current variation/difficulty. */ function getCurrentTreeNode():TreeViewNode { return difficultyToolboxTree.findNodeByPath('stv_song/stv_variation_$chartEditorState.selectedVariation/stv_difficulty_${chartEditorState.selectedVariation}_$chartEditorState.selectedDifficulty', 'id'); } /** * Called when an item in the tree is selected. Updates the current variation/difficulty. */ function onTreeChange(event:UIEvent):Void { // Get the newly selected node. var treeView:TreeView = cast event.target; var targetNode:TreeViewNode = difficultyToolboxTree.selectedNode; if (targetNode == null) { trace('No target node!'); // Reset the user's selection. refreshTreeSelection(); return; } switch (targetNode.data.id.split('_')[1]) { case 'difficulty': var variation:String = targetNode.data.id.split('_')[2]; var difficulty:String = targetNode.data.id.split('_')[3]; if (variation != null && difficulty != null) { trace('Changing difficulty to "$variation:$difficulty"'); chartEditorState.selectedVariation = variation; chartEditorState.selectedDifficulty = difficulty; chartEditorState.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); refreshTreeSelection(); } // case 'song': // case 'variation': default: // Reset the user's selection. trace('Selected wrong node type, resetting selection.'); refreshTreeSelection(); chartEditorState.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); } } public override function refresh():Void { super.refresh(); refreshTreeSelection(); } public static function build(chartEditorState:ChartEditorState):ChartEditorDifficultyToolbox { return new ChartEditorDifficultyToolbox(chartEditorState); } }