1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-09-07 14:38:06 +00:00
Funkin/source/funkin/ui/debug/charting/toolboxes/ChartEditorDifficultyToolbox.hx
Lasercar 9df5395ff8 Load meta/chartdata fix
Deletes the song serialiser class, it has no use anymore
Also fixes the BPM changes
Note style is properly set when metadata toolbox is refreshed
Add variation dialog now fills note style dropdown with note styles and sets it properly afterwards
Also makes a few optimisations for the chart editor
2025-06-11 17:32:44 -07:00

306 lines
11 KiB
Haxe

package funkin.ui.debug.charting.toolboxes;
import funkin.data.song.SongData.SongChartData;
import funkin.data.song.SongData.SongMetadata;
import funkin.data.song.SongRegistry;
import haxe.ui.components.Button;
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.VersionUtil;
import funkin.util.FileUtil;
import openfl.net.FileReference;
import haxe.ui.containers.dialogs.MessageBox.MessageBoxType;
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.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(),
function(notification:String) {
switch (notification)
{
case "success":
chartEditorState.success("Saved Metadata", 'Successfully wrote file (${chartEditorState.currentSongId}$vari-metadata.json).');
case "info":
chartEditorState.info("Canceled Save Metadata", '(${chartEditorState.currentSongId}$vari-metadata.json)');
case "error":
chartEditorState.error("Failure", 'Failed to write file (${chartEditorState.currentSongId}$vari-metadata.json).');
}
});
};
difficultyToolboxSaveChart.onClick = function(_:UIEvent) {
var vari:String = chartEditorState.selectedVariation != Constants.DEFAULT_VARIATION ? '-${chartEditorState.selectedVariation}' : '';
FileUtil.writeFileReference('${chartEditorState.currentSongId}$vari-chart.json', chartEditorState.currentSongChartData.serialize(),
function(notification:String) {
switch (notification)
{
case "success":
chartEditorState.success("Saved Chart Data", 'Successfully wrote file (${chartEditorState.currentSongId}$vari-chart.json).');
case "info":
chartEditorState.info("Canceled Save Chart Data", '(${chartEditorState.currentSongId}$vari-chart.json)');
case "error":
chartEditorState.error("Failure", 'Failed to write file (${chartEditorState.currentSongId}$vari-chart.json).');
}
});
};
difficultyToolboxLoadMetadata.onClick = function(_:UIEvent) {
// Replace metadata for current variation.
FileUtil.browseFileReference(function(fileReference:FileReference) {
var data = fileReference.data.toString();
if (data == null) return;
var songMetadataVersion:Null<thx.semver.Version> = VersionUtil.getVersionFromJSON(data);
var songMetadata:Null<SongMetadata> = null;
if (VersionUtil.validateVersion(songMetadataVersion,
SongRegistry.SONG_METADATA_VERSION_RULE)) songMetadata = SongRegistry.instance.parseEntryMetadataRawWithMigration(data, fileReference.name,
songMetadataVersion);
if (songMetadata != null)
{
chartEditorState.currentSongMetadata = songMetadata;
chartEditorState.healthIconsDirty = true;
chartEditorState.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
chartEditorState.success('Replaced Metadata', 'Replaced metadata with file (${fileReference.name})');
}
else
{
chartEditorState.error('Failure', 'Failed to load metadata file (${fileReference.name})');
}
});
};
difficultyToolboxLoadChart.onClick = function(_:UIEvent) {
// Replace chart data for current variation.
FileUtil.browseFileReference(function(fileReference:FileReference) {
var data = fileReference.data.toString();
if (data == null) return;
var songChartDataVersion:Null<thx.semver.Version> = VersionUtil.getVersionFromJSON(data);
var songChartData:Null<SongChartData> = null;
if (VersionUtil.validateVersion(songChartDataVersion,
SongRegistry.SONG_CHART_DATA_VERSION_RULE)) songChartData = SongRegistry.instance.parseEntryChartDataRawWithMigration(data, fileReference.name,
songChartDataVersion);
if (songChartData != null)
{
chartEditorState.currentSongChartData = songChartData;
chartEditorState.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
updateTree();
refresh();
chartEditorState.success('Loaded Chart Data', 'Loaded chart data file (${fileReference.name})');
if (chartEditorState.currentNoteSelection != []) chartEditorState.currentNoteSelection = [];
if (chartEditorState.currentEventSelection != []) chartEditorState.currentEventSelection = [];
chartEditorState.noteDisplayDirty = true;
chartEditorState.notePreviewDirty = true;
chartEditorState.noteTooltipsDirty = true;
chartEditorState.notePreviewViewportBoundsDirty = true;
}
else
{
chartEditorState.error('Failure', 'Failed to load chart data file (${fileReference.name})');
}
});
};
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<SongMetadata> = 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<String> = 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);
}
}