mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 01:55:52 -05:00
Merge remote-tracking branch 'origin/rewrite/master' into bugfix/chart-editor-macro-rework
This commit is contained in:
commit
4e1945b373
12 changed files with 503 additions and 346 deletions
|
@ -177,6 +177,23 @@ abstract Save(RawSaveData)
|
||||||
return this.optionsChartEditor.previousFiles;
|
return this.optionsChartEditor.previousFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var chartEditorHasBackup(get, set):Bool;
|
||||||
|
|
||||||
|
function get_chartEditorHasBackup():Bool
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.hasBackup == null) this.optionsChartEditor.hasBackup = false;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.hasBackup;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorHasBackup(value:Bool):Bool
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.hasBackup = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.hasBackup;
|
||||||
|
}
|
||||||
|
|
||||||
public var chartEditorNoteQuant(get, set):Int;
|
public var chartEditorNoteQuant(get, set):Int;
|
||||||
|
|
||||||
function get_chartEditorNoteQuant():Int
|
function get_chartEditorNoteQuant():Int
|
||||||
|
@ -926,6 +943,13 @@ typedef SaveControlsData =
|
||||||
*/
|
*/
|
||||||
typedef SaveDataChartEditorOptions =
|
typedef SaveDataChartEditorOptions =
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Whether the Chart Editor created a backup the last time it closed.
|
||||||
|
* Prompt the user to load it, then set this back to `false`.
|
||||||
|
* @default `false`
|
||||||
|
*/
|
||||||
|
var ?hasBackup:Bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Previous files opened in the Chart Editor.
|
* Previous files opened in the Chart Editor.
|
||||||
* @default `[]`
|
* @default `[]`
|
||||||
|
|
|
@ -8,6 +8,7 @@ import funkin.ui.TextMenuList;
|
||||||
import funkin.ui.debug.charting.ChartEditorState;
|
import funkin.ui.debug.charting.ChartEditorState;
|
||||||
import funkin.ui.MusicBeatSubState;
|
import funkin.ui.MusicBeatSubState;
|
||||||
import funkin.util.logging.CrashHandler;
|
import funkin.util.logging.CrashHandler;
|
||||||
|
import flixel.addons.transition.FlxTransitionableState;
|
||||||
|
|
||||||
class DebugMenuSubState extends MusicBeatSubState
|
class DebugMenuSubState extends MusicBeatSubState
|
||||||
{
|
{
|
||||||
|
@ -84,6 +85,8 @@ class DebugMenuSubState extends MusicBeatSubState
|
||||||
|
|
||||||
function openChartEditor()
|
function openChartEditor()
|
||||||
{
|
{
|
||||||
|
FlxTransitionableState.skipNextTransIn = true;
|
||||||
|
|
||||||
FlxG.switchState(new ChartEditorState());
|
FlxG.switchState(new ChartEditorState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package funkin.ui.debug.charting;
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
|
import funkin.util.logging.CrashHandler;
|
||||||
|
import haxe.ui.containers.menus.MenuBar;
|
||||||
import flixel.addons.display.FlxSliceSprite;
|
import flixel.addons.display.FlxSliceSprite;
|
||||||
import flixel.addons.display.FlxTiledSprite;
|
import flixel.addons.display.FlxTiledSprite;
|
||||||
import flixel.addons.transition.FlxTransitionableState;
|
import flixel.addons.transition.FlxTransitionableState;
|
||||||
|
@ -39,6 +41,14 @@ import funkin.play.components.HealthIcon;
|
||||||
import funkin.play.notes.NoteSprite;
|
import funkin.play.notes.NoteSprite;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
import funkin.play.song.Song;
|
import funkin.play.song.Song;
|
||||||
|
import funkin.data.song.SongData.SongChartData;
|
||||||
|
import funkin.data.song.SongRegistry;
|
||||||
|
import funkin.data.song.SongData.SongEventData;
|
||||||
|
import funkin.data.song.SongData.SongMetadata;
|
||||||
|
import funkin.data.song.SongData.SongNoteData;
|
||||||
|
import funkin.data.song.SongData.SongCharacterData;
|
||||||
|
import funkin.data.song.SongDataUtils;
|
||||||
|
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||||
import funkin.play.stage.StageData;
|
import funkin.play.stage.StageData;
|
||||||
import funkin.save.Save;
|
import funkin.save.Save;
|
||||||
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
||||||
|
@ -99,8 +109,6 @@ import haxe.ui.events.MouseEvent;
|
||||||
import haxe.ui.events.UIEvent;
|
import haxe.ui.events.UIEvent;
|
||||||
import haxe.ui.events.UIEvent;
|
import haxe.ui.events.UIEvent;
|
||||||
import haxe.ui.focus.FocusManager;
|
import haxe.ui.focus.FocusManager;
|
||||||
import haxe.ui.notifications.NotificationManager;
|
|
||||||
import haxe.ui.notifications.NotificationType;
|
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
|
|
||||||
using Lambda;
|
using Lambda;
|
||||||
|
@ -777,6 +785,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
return saveDataDirty;
|
return saveDataDirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var shouldShowBackupAvailableDialog(get, set):Bool;
|
||||||
|
|
||||||
|
function get_shouldShowBackupAvailableDialog():Bool
|
||||||
|
{
|
||||||
|
return Save.get().chartEditorHasBackup;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_shouldShowBackupAvailableDialog(value:Bool):Bool
|
||||||
|
{
|
||||||
|
return Save.get().chartEditorHasBackup = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the difficulty tree view in the toolbox has been modified and needs to be updated.
|
* Whether the difficulty tree view in the toolbox has been modified and needs to be updated.
|
||||||
* This happens when we add/remove difficulties.
|
* This happens when we add/remove difficulties.
|
||||||
|
@ -1563,7 +1583,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
buildAdditionalUI();
|
buildAdditionalUI();
|
||||||
populateOpenRecentMenu();
|
populateOpenRecentMenu();
|
||||||
ChartEditorShortcutHandler.applyPlatformShortcutText(this);
|
this.applyPlatformShortcutText();
|
||||||
|
|
||||||
// Setup the onClick listeners for the UI after it's been created.
|
// Setup the onClick listeners for the UI after it's been created.
|
||||||
setupUIListeners();
|
setupUIListeners();
|
||||||
|
@ -1576,33 +1596,28 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
if (params != null && params.fnfcTargetPath != null)
|
if (params != null && params.fnfcTargetPath != null)
|
||||||
{
|
{
|
||||||
// Chart editor was opened from the command line. Open the FNFC file now!
|
// Chart editor was opened from the command line. Open the FNFC file now!
|
||||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(this, params.fnfcTargetPath);
|
var result:Null<Array<String>> = this.loadFromFNFCPath(params.fnfcTargetPath);
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
#if !mac
|
if (result.length == 0)
|
||||||
NotificationManager.instance.addNotification(
|
{
|
||||||
{
|
this.success('Loaded Chart', 'Loaded chart (${params.fnfcTargetPath})');
|
||||||
title: 'Success',
|
}
|
||||||
body: result.length == 0 ? 'Loaded chart (${params.fnfcTargetPath})' : 'Loaded chart (${params.fnfcTargetPath})\n${result.join("\n")}',
|
else
|
||||||
type: result.length == 0 ? NotificationType.Success : NotificationType.Warning,
|
{
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
this.warning('Loaded Chart', 'Loaded chart with issues (${params.fnfcTargetPath})\n${result.join("\n")}');
|
||||||
});
|
}
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if !mac
|
this.error('Failure', 'Failed to load chart (${params.fnfcTargetPath})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Failed to load chart (${params.fnfcTargetPath})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
|
|
||||||
// Song failed to load, open the Welcome dialog so we aren't in a broken state.
|
// Song failed to load, open the Welcome dialog so we aren't in a broken state.
|
||||||
ChartEditorDialogHandler.openWelcomeDialog(this, false);
|
var welcomeDialog = this.openWelcomeDialog(false);
|
||||||
|
if (shouldShowBackupAvailableDialog)
|
||||||
|
{
|
||||||
|
this.openBackupAvailableDialog(welcomeDialog);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (params != null && params.targetSongId != null)
|
else if (params != null && params.targetSongId != null)
|
||||||
|
@ -1611,7 +1626,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ChartEditorDialogHandler.openWelcomeDialog(this, false);
|
var welcomeDialog = this.openWelcomeDialog(false);
|
||||||
|
if (shouldShowBackupAvailableDialog)
|
||||||
|
{
|
||||||
|
this.openBackupAvailableDialog(welcomeDialog);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1649,7 +1668,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// audioVocalTrackGroup.pitch = save.chartEditorPlaybackSpeed;
|
// audioVocalTrackGroup.pitch = save.chartEditorPlaybackSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function writePreferences():Void
|
public function writePreferences(hasBackup:Bool):Void
|
||||||
{
|
{
|
||||||
var save:Save = Save.get();
|
var save:Save = Save.get();
|
||||||
|
|
||||||
|
@ -1657,8 +1676,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
var filteredWorkingFilePaths:Array<String> = [];
|
var filteredWorkingFilePaths:Array<String> = [];
|
||||||
for (chartPath in previousWorkingFilePaths)
|
for (chartPath in previousWorkingFilePaths)
|
||||||
if (chartPath != null) filteredWorkingFilePaths.push(chartPath);
|
if (chartPath != null) filteredWorkingFilePaths.push(chartPath);
|
||||||
|
|
||||||
save.chartEditorPreviousFiles = filteredWorkingFilePaths;
|
save.chartEditorPreviousFiles = filteredWorkingFilePaths;
|
||||||
|
|
||||||
|
if (hasBackup) trace('Queuing backup prompt for next time!');
|
||||||
|
save.chartEditorHasBackup = hasBackup;
|
||||||
|
|
||||||
save.chartEditorNoteQuant = noteSnapQuantIndex;
|
save.chartEditorNoteQuant = noteSnapQuantIndex;
|
||||||
save.chartEditorLiveInputStyle = currentLiveInputStyle;
|
save.chartEditorLiveInputStyle = currentLiveInputStyle;
|
||||||
save.chartEditorDownscroll = isViewDownscroll;
|
save.chartEditorDownscroll = isViewDownscroll;
|
||||||
|
@ -1690,36 +1712,27 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
stopWelcomeMusic();
|
stopWelcomeMusic();
|
||||||
|
|
||||||
// Load chart from file
|
// Load chart from file
|
||||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(this, chartPath);
|
var result:Null<Array<String>> = this.loadFromFNFCPath(chartPath);
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
#if !mac
|
if (result.length == 0)
|
||||||
NotificationManager.instance.addNotification(
|
{
|
||||||
{
|
this.success('Loaded Chart', 'Loaded chart (${chartPath.toString()})');
|
||||||
title: 'Success',
|
}
|
||||||
body: result.length == 0 ? 'Loaded chart (${chartPath.toString()})' : 'Loaded chart (${chartPath.toString()})\n${result.join("\n")}',
|
else
|
||||||
type: result.length == 0 ? NotificationType.Success : NotificationType.Warning,
|
{
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
this.warning('Loaded Chart', 'Loaded chart with issues (${chartPath.toString()})\n${result.join("\n")}');
|
||||||
});
|
}
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if !mac
|
this.error('Failure', 'Failed to load chart (${chartPath.toString()})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Failed to load chart (${chartPath.toString()})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FileUtil.doesFileExist(chartPath))
|
if (!FileUtil.doesFileExist(chartPath))
|
||||||
{
|
{
|
||||||
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
|
trace('Previously loaded chart file (${chartPath.toString()}) does not exist, disabling link...');
|
||||||
menuItemRecentChart.disabled = true;
|
menuItemRecentChart.disabled = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -2017,16 +2030,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playbarHeadLayout.playbarHead.onDrag = function(_:DragEvent) {
|
||||||
|
if (playbarHeadDragging)
|
||||||
|
{
|
||||||
|
var value:Null<Float> = playbarHeadLayout.playbarHead?.value;
|
||||||
|
|
||||||
|
// Set the song position to where the playhead was moved to.
|
||||||
|
scrollPositionInPixels = songLengthInPixels * ((value ?? 0.0) / 100);
|
||||||
|
// Update the conductor and audio tracks to match.
|
||||||
|
moveSongToScrollPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
playbarHeadLayout.playbarHead.onDragEnd = function(_:DragEvent) {
|
playbarHeadLayout.playbarHead.onDragEnd = function(_:DragEvent) {
|
||||||
playbarHeadDragging = false;
|
playbarHeadDragging = false;
|
||||||
|
|
||||||
var value:Null<Float> = playbarHeadLayout?.playbarHead?.value;
|
|
||||||
|
|
||||||
// Set the song position to where the playhead was moved to.
|
|
||||||
scrollPositionInPixels = songLengthInPixels * ((value ?? 0.0) / 100);
|
|
||||||
// Update the conductor and audio tracks to match.
|
|
||||||
moveSongToScrollPosition();
|
|
||||||
|
|
||||||
// If we were dragging the playhead while the song was playing, resume playing.
|
// If we were dragging the playhead while the song was playing, resume playing.
|
||||||
if (playbarHeadDraggingWasPlaying)
|
if (playbarHeadDraggingWasPlaying)
|
||||||
{
|
{
|
||||||
|
@ -2040,9 +2058,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
|
|
||||||
if (!Preferences.debugDisplay) menubar.paddingLeft = null;
|
if (!Preferences.debugDisplay) menubar.paddingLeft = null;
|
||||||
|
|
||||||
// Setup notifications.
|
this.setupNotifications();
|
||||||
@:privateAccess
|
|
||||||
NotificationManager.GUTTER_SIZE = 20;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2071,19 +2087,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Add functionality to the menu items.
|
// Add functionality to the menu items.
|
||||||
|
|
||||||
// File
|
// File
|
||||||
menubarItemNewChart.onClick = _ -> ChartEditorDialogHandler.openWelcomeDialog(this, true);
|
menubarItemNewChart.onClick = _ -> this.openWelcomeDialog(true);
|
||||||
menubarItemOpenChart.onClick = _ -> ChartEditorDialogHandler.openBrowseFNFC(this, true);
|
menubarItemOpenChart.onClick = _ -> this.openBrowseFNFC(true);
|
||||||
menubarItemSaveChart.onClick = _ -> {
|
menubarItemSaveChart.onClick = _ -> {
|
||||||
if (currentWorkingFilePath != null)
|
if (currentWorkingFilePath != null)
|
||||||
{
|
{
|
||||||
ChartEditorImportExportHandler.exportAllSongData(this, true, currentWorkingFilePath);
|
this.exportAllSongData(true, currentWorkingFilePath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ChartEditorImportExportHandler.exportAllSongData(this, false);
|
this.exportAllSongData(false, null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
menubarItemSaveChartAs.onClick = _ -> ChartEditorImportExportHandler.exportAllSongData(this);
|
menubarItemSaveChartAs.onClick = _ -> this.exportAllSongData(false, null);
|
||||||
menubarItemExit.onClick = _ -> quitChartEditor();
|
menubarItemExit.onClick = _ -> quitChartEditor();
|
||||||
|
|
||||||
// Edit
|
// Edit
|
||||||
|
@ -2175,6 +2191,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
menubarItemAbout.onClick = _ -> this.openAboutDialog();
|
menubarItemAbout.onClick = _ -> this.openAboutDialog();
|
||||||
menubarItemWelcomeDialog.onClick = _ -> this.openWelcomeDialog(true);
|
menubarItemWelcomeDialog.onClick = _ -> this.openWelcomeDialog(true);
|
||||||
|
|
||||||
|
#if sys
|
||||||
|
menubarItemGoToBackupsFolder.onClick = _ -> this.openBackupsFolder();
|
||||||
|
#else
|
||||||
|
// Disable if no file system or command access
|
||||||
|
menubarItemGoToBackupsFolder.disabled = true;
|
||||||
|
#end
|
||||||
|
|
||||||
menubarItemUserGuide.onClick = _ -> this.openUserGuideDialog();
|
menubarItemUserGuide.onClick = _ -> this.openUserGuideDialog();
|
||||||
|
|
||||||
menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value;
|
menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value;
|
||||||
|
@ -2265,7 +2288,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
function setupAutoSave():Void
|
function setupAutoSave():Void
|
||||||
{
|
{
|
||||||
|
// Called when clicking the X button on the window.
|
||||||
WindowUtil.windowExit.add(onWindowClose);
|
WindowUtil.windowExit.add(onWindowClose);
|
||||||
|
|
||||||
|
// Called when the game crashes.
|
||||||
|
CrashHandler.errorSignal.add(onWindowCrash);
|
||||||
|
CrashHandler.criticalErrorSignal.add(onWindowCrash);
|
||||||
|
|
||||||
saveDataDirty = false;
|
saveDataDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2274,10 +2303,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
function autoSave():Void
|
function autoSave():Void
|
||||||
{
|
{
|
||||||
|
var needsAutoSave:Bool = saveDataDirty;
|
||||||
|
|
||||||
saveDataDirty = false;
|
saveDataDirty = false;
|
||||||
|
|
||||||
// Auto-save preferences.
|
// Auto-save preferences.
|
||||||
writePreferences();
|
writePreferences(needsAutoSave);
|
||||||
|
|
||||||
// Auto-save the chart.
|
// Auto-save the chart.
|
||||||
#if html5
|
#if html5
|
||||||
|
@ -2285,24 +2316,72 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// TODO: Implement this.
|
// TODO: Implement this.
|
||||||
#else
|
#else
|
||||||
// Auto-save to temp file.
|
// Auto-save to temp file.
|
||||||
ChartEditorImportExportHandler.exportAllSongData(this, true);
|
if (needsAutoSave)
|
||||||
|
{
|
||||||
|
this.exportAllSongData(true, null);
|
||||||
|
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
||||||
|
this.infoWithActions('Auto-Save', 'Chart auto-saved to ${absoluteBackupsPath}.', [
|
||||||
|
{
|
||||||
|
text: "Take Me There",
|
||||||
|
callback: openBackupsFolder,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the backups folder in the file explorer.
|
||||||
|
* Don't call this on HTML5.
|
||||||
|
*/
|
||||||
|
function openBackupsFolder():Void
|
||||||
|
{
|
||||||
|
// TODO: Is there a way to open a folder and highlight a file in it?
|
||||||
|
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
||||||
|
WindowUtil.openFolder(absoluteBackupsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the window was closed, to save a backup of the chart.
|
||||||
|
* @param exitCode The exit code of the window. We use `-1` when calling the function due to a game crash.
|
||||||
|
*/
|
||||||
function onWindowClose(exitCode:Int):Void
|
function onWindowClose(exitCode:Int):Void
|
||||||
{
|
{
|
||||||
trace('Window exited with exit code: $exitCode');
|
trace('Window exited with exit code: $exitCode');
|
||||||
trace('Should save chart? $saveDataDirty');
|
trace('Should save chart? $saveDataDirty');
|
||||||
|
|
||||||
if (saveDataDirty)
|
var needsAutoSave:Bool = saveDataDirty;
|
||||||
|
|
||||||
|
writePreferences(needsAutoSave);
|
||||||
|
|
||||||
|
if (needsAutoSave)
|
||||||
{
|
{
|
||||||
ChartEditorImportExportHandler.exportAllSongData(this, true);
|
this.exportAllSongData(true, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWindowCrash(message:String):Void
|
||||||
|
{
|
||||||
|
trace('Chart editor intercepted crash:');
|
||||||
|
trace('${message}');
|
||||||
|
|
||||||
|
trace('Should save chart? $saveDataDirty');
|
||||||
|
|
||||||
|
var needsAutoSave:Bool = saveDataDirty;
|
||||||
|
|
||||||
|
writePreferences(needsAutoSave);
|
||||||
|
|
||||||
|
if (needsAutoSave)
|
||||||
|
{
|
||||||
|
this.exportAllSongData(true, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupAutoSave():Void
|
function cleanupAutoSave():Void
|
||||||
{
|
{
|
||||||
WindowUtil.windowExit.remove(onWindowClose);
|
WindowUtil.windowExit.remove(onWindowClose);
|
||||||
|
CrashHandler.errorSignal.remove(onWindowCrash);
|
||||||
|
CrashHandler.criticalErrorSignal.remove(onWindowCrash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override function update(elapsed:Float):Void
|
public override function update(elapsed:Float):Void
|
||||||
|
@ -2989,9 +3068,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
if (FlxG.mouse.justReleased) FlxG.sound.play(Paths.sound("chartingSounds/ClickUp"));
|
if (FlxG.mouse.justReleased) FlxG.sound.play(Paths.sound("chartingSounds/ClickUp"));
|
||||||
|
|
||||||
// Note: If a menu is open in HaxeUI, don't handle cursor behavior.
|
// Note: If a menu is open in HaxeUI, don't handle cursor behavior.
|
||||||
var shouldHandleCursor:Bool = !(isHaxeUIFocused || playbarHeadDragging)
|
var shouldHandleCursor:Bool = !(isHaxeUIFocused || playbarHeadDragging || isHaxeUIDialogOpen)
|
||||||
|| (selectionBoxStartPos != null)
|
|| (selectionBoxStartPos != null)
|
||||||
|| (dragTargetNote != null || dragTargetEvent != null);
|
|| (dragTargetNote != null || dragTargetEvent != null);
|
||||||
|
|
||||||
var eventColumn:Int = (STRUMLINE_SIZE * 2 + 1) - 1;
|
var eventColumn:Int = (STRUMLINE_SIZE * 2 + 1) - 1;
|
||||||
|
|
||||||
// trace('shouldHandleCursor: $shouldHandleCursor');
|
// trace('shouldHandleCursor: $shouldHandleCursor');
|
||||||
|
@ -3460,6 +3540,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
// Finished dragging. Release the note.
|
// Finished dragging. Release the note.
|
||||||
currentPlaceNoteData = null;
|
currentPlaceNoteData = null;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Cursor should be a grabby hand.
|
||||||
|
if (targetCursorMode == null) targetCursorMode = Grabbing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -3999,20 +4084,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
if (currentWorkingFilePath == null || FlxG.keys.pressed.SHIFT)
|
if (currentWorkingFilePath == null || FlxG.keys.pressed.SHIFT)
|
||||||
{
|
{
|
||||||
// CTRL + SHIFT + S = Save As
|
// CTRL + SHIFT + S = Save As
|
||||||
ChartEditorImportExportHandler.exportAllSongData(this, false);
|
this.exportAllSongData(false, null, function(path:String) {
|
||||||
|
// CTRL + SHIFT + S Successful
|
||||||
|
this.success('Saved Chart', 'Chart saved successfully to ${path}.');
|
||||||
|
}, function() {
|
||||||
|
// CTRL + SHIFT + S Cancelled
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// CTRL + S = Save Chart
|
// CTRL + S = Save Chart
|
||||||
ChartEditorImportExportHandler.exportAllSongData(this, true, currentWorkingFilePath);
|
this.exportAllSongData(true, currentWorkingFilePath);
|
||||||
|
this.success('Saved Chart', 'Chart saved successfully to ${currentWorkingFilePath}.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.S && !isHaxeUIDialogOpen)
|
|
||||||
{
|
|
||||||
this.exportAllSongData(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// CTRL + Q = Quit to Menu
|
// CTRL + Q = Quit to Menu
|
||||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.Q)
|
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.Q)
|
||||||
{
|
{
|
||||||
|
@ -4173,6 +4259,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
{
|
{
|
||||||
// F1 = Open Help
|
// F1 = Open Help
|
||||||
if (FlxG.keys.justPressed.F1) this.openUserGuideDialog();
|
if (FlxG.keys.justPressed.F1) this.openUserGuideDialog();
|
||||||
|
|
||||||
|
// DEBUG KEYBIND: Ctrl + Alt + Shift + L = Crash the game.
|
||||||
|
#if debug
|
||||||
|
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L)
|
||||||
|
{
|
||||||
|
throw "DEBUG: Crashing the chart editor!";
|
||||||
|
}
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
override function handleQuickWatch():Void
|
override function handleQuickWatch():Void
|
||||||
|
@ -4509,15 +4603,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !mac
|
this.success('Switch Difficulty', 'Switched difficulty to ${selectedDifficulty.toTitleCase()}');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Switch Difficulty',
|
|
||||||
body: 'Switched difficulty to ${selectedDifficulty.toTitleCase()}',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4779,9 +4865,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
*/
|
*/
|
||||||
// ====================
|
// ====================
|
||||||
|
|
||||||
/**
|
|
||||||
* Dismiss any existing HaxeUI notifications, if there are any.
|
|
||||||
*/
|
|
||||||
function handleNotePreview():Void
|
function handleNotePreview():Void
|
||||||
{
|
{
|
||||||
if (notePreviewDirty && notePreview != null)
|
if (notePreviewDirty && notePreview != null)
|
||||||
|
@ -4964,14 +5047,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||||
ChartEditorNoteSprite.noteFrameCollection = null;
|
ChartEditorNoteSprite.noteFrameCollection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Dismiss any existing notifications, if there are any.
|
|
||||||
*/
|
|
||||||
function dismissNotifications():Void
|
|
||||||
{
|
|
||||||
NotificationManager.instance.clearNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyCanQuickSave():Void
|
function applyCanQuickSave():Void
|
||||||
{
|
{
|
||||||
if (menubarItemSaveChart == null) return;
|
if (menubarItemSaveChart == null) return;
|
||||||
|
|
|
@ -4,8 +4,6 @@ import funkin.data.song.SongData.SongEventData;
|
||||||
import funkin.data.song.SongData.SongNoteData;
|
import funkin.data.song.SongData.SongNoteData;
|
||||||
import funkin.data.song.SongDataUtils;
|
import funkin.data.song.SongDataUtils;
|
||||||
import funkin.data.song.SongDataUtils.SongClipboardItems;
|
import funkin.data.song.SongDataUtils.SongClipboardItems;
|
||||||
import haxe.ui.notifications.NotificationManager;
|
|
||||||
import haxe.ui.notifications.NotificationType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command which inserts the contents of the clipboard into the chart editor.
|
* A command which inserts the contents of the clipboard into the chart editor.
|
||||||
|
@ -30,15 +28,7 @@ class PasteItemsCommand implements ChartEditorCommand
|
||||||
|
|
||||||
if (currentClipboard.valid != true)
|
if (currentClipboard.valid != true)
|
||||||
{
|
{
|
||||||
#if !mac
|
state.error('Failed to Paste', 'Could not parse clipboard contents.');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failed to Paste',
|
|
||||||
body: 'Could not parse clipboard contents.',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,15 +48,7 @@ class PasteItemsCommand implements ChartEditorCommand
|
||||||
|
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
|
|
||||||
#if !mac
|
state.success('Paste Successful', 'Successfully pasted clipboard contents.');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Paste Successful',
|
|
||||||
body: 'Successfully pasted clipboard contents.',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function undo(state:ChartEditorState):Void
|
public function undo(state:ChartEditorState):Void
|
||||||
|
|
|
@ -14,16 +14,18 @@ import funkin.play.character.CharacterData;
|
||||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.song.Song;
|
import funkin.play.song.Song;
|
||||||
import funkin.play.stage.StageData;
|
import funkin.play.stage.StageData;
|
||||||
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget;
|
|
||||||
import funkin.ui.debug.charting.dialogs.ChartEditorAboutDialog;
|
import funkin.ui.debug.charting.dialogs.ChartEditorAboutDialog;
|
||||||
|
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget;
|
||||||
import funkin.ui.debug.charting.dialogs.ChartEditorUploadChartDialog;
|
import funkin.ui.debug.charting.dialogs.ChartEditorUploadChartDialog;
|
||||||
import funkin.ui.debug.charting.dialogs.ChartEditorWelcomeDialog;
|
import funkin.ui.debug.charting.dialogs.ChartEditorWelcomeDialog;
|
||||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||||
import funkin.util.Constants;
|
import funkin.util.Constants;
|
||||||
|
import funkin.util.DateUtil;
|
||||||
import funkin.util.FileUtil;
|
import funkin.util.FileUtil;
|
||||||
import funkin.util.SerializerUtil;
|
import funkin.util.SerializerUtil;
|
||||||
import funkin.util.SortUtil;
|
import funkin.util.SortUtil;
|
||||||
import funkin.util.VersionUtil;
|
import funkin.util.VersionUtil;
|
||||||
|
import funkin.util.WindowUtil;
|
||||||
import haxe.io.Path;
|
import haxe.io.Path;
|
||||||
import haxe.ui.components.Button;
|
import haxe.ui.components.Button;
|
||||||
import haxe.ui.components.DropDown;
|
import haxe.ui.components.DropDown;
|
||||||
|
@ -66,6 +68,7 @@ class ChartEditorDialogHandler
|
||||||
static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide');
|
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_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_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.
|
* Builds and opens a dialog giving brief credits for the chart editor.
|
||||||
|
@ -397,15 +400,7 @@ class ChartEditorDialogHandler
|
||||||
{
|
{
|
||||||
if (state.loadInstFromBytes(selectedFile.bytes, instId))
|
if (state.loadInstFromBytes(selectedFile.bytes, instId))
|
||||||
{
|
{
|
||||||
#if !mac
|
state.success('Loaded Instrumental', 'Loaded instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
|
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
|
@ -413,15 +408,7 @@ class ChartEditorDialogHandler
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#if !mac
|
state.error('Failed to Load Instrumental', 'Failed to load instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Failed to load instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -433,15 +420,7 @@ class ChartEditorDialogHandler
|
||||||
if (state.loadInstFromPath(path, instId))
|
if (state.loadInstFromPath(path, instId))
|
||||||
{
|
{
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
state.success('Loaded Instrumental', 'Loaded instrumental track (${path.file}.${path.ext}) for variation (${state.selectedVariation})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded instrumental track (${path.file}.${path.ext}) for variation (${state.selectedVariation})',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
|
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
|
@ -459,15 +438,7 @@ class ChartEditorDialogHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
state.error('Failed to Load Instrumental', message);
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: message,
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -693,15 +664,7 @@ class ChartEditorDialogHandler
|
||||||
if (state.loadVocalsFromPath(path, charKey, instId))
|
if (state.loadVocalsFromPath(path, charKey, instId))
|
||||||
{
|
{
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
state.success('Loaded Vocals', 'Loaded vocals for $charName (${path.file}.${path.ext}), variation ${state.selectedVariation}');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded vocals for $charName (${path.file}.${path.ext}), variation ${state.selectedVariation}',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
#if FILE_DROP_SUPPORTED
|
#if FILE_DROP_SUPPORTED
|
||||||
vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||||
#else
|
#else
|
||||||
|
@ -715,16 +678,7 @@ class ChartEditorDialogHandler
|
||||||
{
|
{
|
||||||
trace('Failed to load vocal track (${path.file}.${path.ext})');
|
trace('Failed to load vocal track (${path.file}.${path.ext})');
|
||||||
|
|
||||||
// Vocals failed to load.
|
state.error('Failed to Load Vocals', 'Failed to load vocal track (${path.file}.${path.ext}) for variation (${state.selectedVariation})');
|
||||||
#if !mac
|
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Failed to load vocal track (${path.file}.${path.ext}) for variation (${state.selectedVariation})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if FILE_DROP_SUPPORTED
|
#if FILE_DROP_SUPPORTED
|
||||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||||
|
@ -750,15 +704,8 @@ class ChartEditorDialogHandler
|
||||||
if (state.loadVocalsFromBytes(selectedFile.bytes, charKey, instId))
|
if (state.loadVocalsFromBytes(selectedFile.bytes, charKey, instId))
|
||||||
{
|
{
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
state.success('Loaded Vocals', 'Loaded vocals for $charName (${selectedFile.name}), variation ${state.selectedVariation}');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded vocals for $charName (${selectedFile.name}), variation ${state.selectedVariation}',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
#if FILE_DROP_SUPPORTED
|
#if FILE_DROP_SUPPORTED
|
||||||
vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
vocalsEntryLabel.text = 'Voices for $charName (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||||
#else
|
#else
|
||||||
|
@ -771,15 +718,7 @@ class ChartEditorDialogHandler
|
||||||
{
|
{
|
||||||
trace('Failed to load vocal track (${selectedFile.fullPath})');
|
trace('Failed to load vocal track (${selectedFile.fullPath})');
|
||||||
|
|
||||||
#if !mac
|
state.error('Failed to Load Vocals', 'Failed to load vocal track (${selectedFile.name}) for variation (${state.selectedVariation})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Failed to load vocal track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if FILE_DROP_SUPPORTED
|
#if FILE_DROP_SUPPORTED
|
||||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||||
|
@ -934,15 +873,7 @@ class ChartEditorDialogHandler
|
||||||
if (songMetadataVersion == null)
|
if (songMetadataVersion == null)
|
||||||
{
|
{
|
||||||
// Tell the user the load was not successful.
|
// Tell the user the load was not successful.
|
||||||
#if !mac
|
state.error('Failure', 'Could not parse metadata file version (${path.file}.${path.ext})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Could not parse metadata file version (${path.file}.${path.ext})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -952,30 +883,14 @@ class ChartEditorDialogHandler
|
||||||
if (songMetadataVariation == null)
|
if (songMetadataVariation == null)
|
||||||
{
|
{
|
||||||
// Tell the user the load was not successful.
|
// Tell the user the load was not successful.
|
||||||
#if !mac
|
state.error('Failure', 'Could not load metadata file (${path.file}.${path.ext})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Could not load metadata file (${path.file}.${path.ext})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
songMetadata.set(variation, songMetadataVariation);
|
songMetadata.set(variation, songMetadataVariation);
|
||||||
|
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
state.success('Loaded Metadata', 'Loaded metadata file (${path.file}.${path.ext})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded metadata file (${path.file}.${path.ext})',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if FILE_DROP_SUPPORTED
|
#if FILE_DROP_SUPPORTED
|
||||||
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||||
|
@ -999,15 +914,7 @@ class ChartEditorDialogHandler
|
||||||
if (songMetadataVersion == null)
|
if (songMetadataVersion == null)
|
||||||
{
|
{
|
||||||
// Tell the user the load was not successful.
|
// Tell the user the load was not successful.
|
||||||
#if !mac
|
state.error('Failure', 'Could not parse metadata file version (${selectedFile.name})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Could not parse metadata file version (${selectedFile.name})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1019,15 +926,7 @@ class ChartEditorDialogHandler
|
||||||
songMetadata.set(variation, songMetadataVariation);
|
songMetadata.set(variation, songMetadataVariation);
|
||||||
|
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
state.success('Loaded Metadata', 'Loaded metadata file (${selectedFile.name})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded metadata file (${selectedFile.name})',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if FILE_DROP_SUPPORTED
|
#if FILE_DROP_SUPPORTED
|
||||||
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
label.text = 'Metadata file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||||
|
@ -1040,15 +939,7 @@ class ChartEditorDialogHandler
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Tell the user the load was unsuccessful.
|
// Tell the user the load was unsuccessful.
|
||||||
#if !mac
|
state.error('Failure', 'Failed to load metadata file (${selectedFile.name})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Failed to load metadata file (${selectedFile.name})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1064,15 +955,7 @@ class ChartEditorDialogHandler
|
||||||
if (songChartDataVersion == null)
|
if (songChartDataVersion == null)
|
||||||
{
|
{
|
||||||
// Tell the user the load was not successful.
|
// Tell the user the load was not successful.
|
||||||
#if !mac
|
state.error('Failure', 'Could not parse chart data file version (${path.file}.${path.ext})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Could not parse chart data file version (${path.file}.${path.ext})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1087,15 +970,7 @@ class ChartEditorDialogHandler
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
state.success('Loaded Chart Data', 'Loaded chart data file (${path.file}.${path.ext})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded chart data file (${path.file}.${path.ext})',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if FILE_DROP_SUPPORTED
|
#if FILE_DROP_SUPPORTED
|
||||||
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||||
|
@ -1106,15 +981,7 @@ class ChartEditorDialogHandler
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Tell the user the load was unsuccessful.
|
// Tell the user the load was unsuccessful.
|
||||||
#if !mac
|
state.error('Failure', 'Failed to load chart data file (${path.file}.${path.ext})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Failed to load chart data file (${path.file}.${path.ext})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1131,15 +998,7 @@ class ChartEditorDialogHandler
|
||||||
if (songChartDataVersion == null)
|
if (songChartDataVersion == null)
|
||||||
{
|
{
|
||||||
// Tell the user the load was not successful.
|
// Tell the user the load was not successful.
|
||||||
#if !mac
|
state.error('Failure', 'Could not parse chart data file version (${selectedFile.name})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Could not parse chart data file version (${selectedFile.name})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1154,15 +1013,7 @@ class ChartEditorDialogHandler
|
||||||
state.noteDisplayDirty = true;
|
state.noteDisplayDirty = true;
|
||||||
|
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
state.success('Loaded Chart Data', 'Loaded chart data file (${selectedFile.name})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded chart data file (${selectedFile.name})',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if FILE_DROP_SUPPORTED
|
#if FILE_DROP_SUPPORTED
|
||||||
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
label.text = 'Chart data file (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||||
|
@ -1259,15 +1110,7 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
if (fnfLegacyData == null)
|
if (fnfLegacyData == null)
|
||||||
{
|
{
|
||||||
#if !mac
|
state.error('Failure', 'Failed to parse FNF chart file (${selectedFile.name})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Failure',
|
|
||||||
body: 'Failed to parse FNF chart file (${selectedFile.name})',
|
|
||||||
type: NotificationType.Error,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1277,15 +1120,7 @@ class ChartEditorDialogHandler
|
||||||
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||||
|
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
#if !mac
|
state.success('Success', 'Loaded chart file (${selectedFile.name})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded chart file (${selectedFile.name})',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1300,15 +1135,7 @@ class ChartEditorDialogHandler
|
||||||
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
state.loadSong([Constants.DEFAULT_VARIATION => songMetadata], [Constants.DEFAULT_VARIATION => songChartData]);
|
||||||
|
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
#if !mac
|
state.success('Success', 'Loaded chart file (${path.file}.${path.ext})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded chart file (${path.file}.${path.ext})',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state.addDropHandler({component: importBox, handler: onDropFile});
|
state.addDropHandler({component: importBox, handler: onDropFile});
|
||||||
|
@ -1408,14 +1235,9 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
state.songMetadata.set(pendingVariation.variation, pendingVariation);
|
state.songMetadata.set(pendingVariation.variation, pendingVariation);
|
||||||
state.difficultySelectDirty = true; // Force the Difficulty toolbox to update.
|
state.difficultySelectDirty = true; // Force the Difficulty toolbox to update.
|
||||||
#if !mac
|
|
||||||
NotificationManager.instance.addNotification(
|
state.success('Add Variation', 'Added new variation "${pendingVariation.variation}"');
|
||||||
{
|
|
||||||
title: "Add Variation",
|
|
||||||
body: 'Added new variation "${pendingVariation.variation}"',
|
|
||||||
type: NotificationType.Success
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1472,14 +1294,8 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
state.createDifficulty(dialogVariation.value.id, dialogDifficultyName.text.toLowerCase(), inputScrollSpeed.value ?? 1.0);
|
state.createDifficulty(dialogVariation.value.id, dialogDifficultyName.text.toLowerCase(), inputScrollSpeed.value ?? 1.0);
|
||||||
|
|
||||||
#if !mac
|
state.success('Add Difficulty', 'Added new difficulty "${dialogDifficultyName.text.toLowerCase()}"');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: "Add Difficulty",
|
|
||||||
body: 'Added new difficulty "${dialogDifficultyName.text.toLowerCase()}"',
|
|
||||||
type: NotificationType.Success
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
dialog.hideDialog(DialogButton.APPLY);
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package funkin.ui.debug.charting.handlers;
|
package funkin.ui.debug.charting.handlers;
|
||||||
|
|
||||||
import funkin.util.VersionUtil;
|
import funkin.util.VersionUtil;
|
||||||
import haxe.ui.notifications.NotificationType;
|
|
||||||
import funkin.util.DateUtil;
|
import funkin.util.DateUtil;
|
||||||
import haxe.io.Path;
|
import haxe.io.Path;
|
||||||
import funkin.util.SerializerUtil;
|
import funkin.util.SerializerUtil;
|
||||||
import haxe.ui.notifications.NotificationManager;
|
import funkin.util.SortUtil;
|
||||||
import funkin.util.FileUtil;
|
import funkin.util.FileUtil;
|
||||||
import funkin.util.FileUtil.FileWriteMode;
|
import funkin.util.FileUtil.FileWriteMode;
|
||||||
import haxe.io.Bytes;
|
import haxe.io.Bytes;
|
||||||
|
@ -22,6 +21,8 @@ import funkin.data.song.importer.ChartManifestData;
|
||||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||||
class ChartEditorImportExportHandler
|
class ChartEditorImportExportHandler
|
||||||
{
|
{
|
||||||
|
public static final BACKUPS_PATH:String = './backups/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch's a song's existing chart and audio and loads it, replacing the current song.
|
* Fetch's a song's existing chart and audio and loads it, replacing the current song.
|
||||||
*/
|
*/
|
||||||
|
@ -100,15 +101,7 @@ class ChartEditorImportExportHandler
|
||||||
|
|
||||||
state.refreshMetadataToolbox();
|
state.refreshMetadataToolbox();
|
||||||
|
|
||||||
#if !mac
|
state.success('Success', 'Loaded song (${rawSongMetadata[0].songName})');
|
||||||
NotificationManager.instance.addNotification(
|
|
||||||
{
|
|
||||||
title: 'Success',
|
|
||||||
body: 'Loaded song (${rawSongMetadata[0].songName})',
|
|
||||||
type: NotificationType.Success,
|
|
||||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
|
||||||
});
|
|
||||||
#end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -318,11 +311,56 @@ class ChartEditorImportExportHandler
|
||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getLatestBackupPath():Null<String>
|
||||||
|
{
|
||||||
|
#if sys
|
||||||
|
var entries:Array<String> = sys.FileSystem.readDirectory(BACKUPS_PATH);
|
||||||
|
entries.sort(SortUtil.alphabetically);
|
||||||
|
|
||||||
|
var latestBackupPath:Null<String> = entries[(entries.length - 1)];
|
||||||
|
|
||||||
|
if (latestBackupPath == null) return null;
|
||||||
|
return haxe.io.Path.join([BACKUPS_PATH, latestBackupPath]);
|
||||||
|
#else
|
||||||
|
return null;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getLatestBackupDate():Null<Date>
|
||||||
|
{
|
||||||
|
#if sys
|
||||||
|
var latestBackupPath:Null<String> = getLatestBackupPath();
|
||||||
|
if (latestBackupPath == null) return null;
|
||||||
|
|
||||||
|
var latestBackupName:String = haxe.io.Path.withoutDirectory(latestBackupPath);
|
||||||
|
latestBackupName = haxe.io.Path.withoutExtension(latestBackupName);
|
||||||
|
|
||||||
|
var parts = latestBackupName.split('-');
|
||||||
|
|
||||||
|
// var chart:String = parts[0];
|
||||||
|
// var editor:String = parts[1];
|
||||||
|
var year:Int = Std.parseInt(parts[2] ?? '0') ?? 0;
|
||||||
|
var month:Int = Std.parseInt(parts[3] ?? '1') ?? 1;
|
||||||
|
var day:Int = Std.parseInt(parts[4] ?? '0') ?? 0;
|
||||||
|
var hour:Int = Std.parseInt(parts[5] ?? '0') ?? 0;
|
||||||
|
var minute:Int = Std.parseInt(parts[6] ?? '0') ?? 0;
|
||||||
|
var second:Int = Std.parseInt(parts[7] ?? '0') ?? 0;
|
||||||
|
|
||||||
|
var date:Date = new Date(year, month - 1, day, hour, minute, second);
|
||||||
|
return date;
|
||||||
|
#else
|
||||||
|
return null;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param force Whether to export without prompting. `false` will prompt the user for a location.
|
* @param force Whether to export without prompting. `false` will prompt the user for a location.
|
||||||
* @param targetPath where to export if `force` is `true`. If `null`, will export to the `backups` folder.
|
* @param targetPath where to export if `force` is `true`. If `null`, will export to the `backups` folder.
|
||||||
|
* @param onSaveCb Callback for when the file is saved.
|
||||||
|
* @param onCancelCb Callback for when saving is cancelled.
|
||||||
*/
|
*/
|
||||||
public static function exportAllSongData(state:ChartEditorState, force:Bool = false, ?targetPath:String):Void
|
public static function exportAllSongData(state:ChartEditorState, force:Bool = false, targetPath:Null<String>, ?onSaveCb:String->Void,
|
||||||
|
?onCancelCb:Void->Void):Void
|
||||||
{
|
{
|
||||||
var zipEntries:Array<haxe.zip.Entry> = [];
|
var zipEntries:Array<haxe.zip.Entry> = [];
|
||||||
|
|
||||||
|
@ -369,13 +407,13 @@ class ChartEditorImportExportHandler
|
||||||
// Force writing to a generic path (autosave or crash recovery)
|
// Force writing to a generic path (autosave or crash recovery)
|
||||||
targetMode = Skip;
|
targetMode = Skip;
|
||||||
targetPath = Path.join([
|
targetPath = Path.join([
|
||||||
'./backups/',
|
BACKUPS_PATH,
|
||||||
'chart-editor-${DateUtil.generateTimestamp()}.${Constants.EXT_CHART}'
|
'chart-editor-${DateUtil.generateTimestamp()}.${Constants.EXT_CHART}'
|
||||||
]);
|
]);
|
||||||
// We have to force write because the program will die before the save dialog is closed.
|
// We have to force write because the program will die before the save dialog is closed.
|
||||||
trace('Force exporting to $targetPath...');
|
trace('Force exporting to $targetPath...');
|
||||||
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
|
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
|
||||||
|
if (onSaveCb != null) onSaveCb(targetPath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -383,7 +421,7 @@ class ChartEditorImportExportHandler
|
||||||
trace('Force exporting to $targetPath...');
|
trace('Force exporting to $targetPath...');
|
||||||
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
|
FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
|
||||||
state.saveDataDirty = false;
|
state.saveDataDirty = false;
|
||||||
|
if (onSaveCb != null) onSaveCb(targetPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -400,11 +438,13 @@ class ChartEditorImportExportHandler
|
||||||
trace('Saved to "${paths[0]}"');
|
trace('Saved to "${paths[0]}"');
|
||||||
state.currentWorkingFilePath = paths[0];
|
state.currentWorkingFilePath = paths[0];
|
||||||
state.applyWindowTitle();
|
state.applyWindowTitle();
|
||||||
|
if (onSaveCb != null) onSaveCb(paths[0]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onCancel:Void->Void = function() {
|
var onCancel:Void->Void = function() {
|
||||||
trace('Export cancelled.');
|
trace('Export cancelled.');
|
||||||
|
if (onCancelCb != null) onCancelCb();
|
||||||
};
|
};
|
||||||
|
|
||||||
trace('Exporting to user-defined location...');
|
trace('Exporting to user-defined location...');
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
package funkin.ui.debug.charting.handlers;
|
||||||
|
|
||||||
|
import haxe.ui.components.Button;
|
||||||
|
import haxe.ui.containers.HBox;
|
||||||
|
import haxe.ui.notifications.Notification;
|
||||||
|
import haxe.ui.notifications.NotificationManager;
|
||||||
|
import haxe.ui.notifications.NotificationType;
|
||||||
|
|
||||||
|
class ChartEditorNotificationHandler
|
||||||
|
{
|
||||||
|
public static function setupNotifications(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
// Setup notifications.
|
||||||
|
@:privateAccess
|
||||||
|
NotificationManager.GUTTER_SIZE = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification with a checkmark indicating success.
|
||||||
|
* @param state The current state of the chart editor.
|
||||||
|
*/
|
||||||
|
public static function success(state:ChartEditorState, title:String, body:String):Notification
|
||||||
|
{
|
||||||
|
return sendNotification(title, body, NotificationType.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification with a warning icon.
|
||||||
|
* @param state The current state of the chart editor.
|
||||||
|
*/
|
||||||
|
public static function warning(state:ChartEditorState, title:String, body:String):Notification
|
||||||
|
{
|
||||||
|
return sendNotification(title, body, NotificationType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification with a warning icon.
|
||||||
|
* @param state The current state of the chart editor.
|
||||||
|
*/
|
||||||
|
public static inline function warn(state:ChartEditorState, title:String, body:String):Notification
|
||||||
|
{
|
||||||
|
return warning(state, title, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification with a cross indicating an error.
|
||||||
|
* @param state The current state of the chart editor.
|
||||||
|
*/
|
||||||
|
public static function error(state:ChartEditorState, title:String, body:String):Notification
|
||||||
|
{
|
||||||
|
return sendNotification(title, body, NotificationType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification with a cross indicating failure.
|
||||||
|
* @param state The current state of the chart editor.
|
||||||
|
*/
|
||||||
|
public static inline function failure(state:ChartEditorState, title:String, body:String):Notification
|
||||||
|
{
|
||||||
|
return error(state, title, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification with an info icon.
|
||||||
|
* @param state The current state of the chart editor.
|
||||||
|
*/
|
||||||
|
public static function info(state:ChartEditorState, title:String, body:String):Notification
|
||||||
|
{
|
||||||
|
return sendNotification(title, body, NotificationType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a notification with an info icon and one or more actions.
|
||||||
|
* @param state The current state of the chart editor.
|
||||||
|
* @param title The title of the notification.
|
||||||
|
* @param body The body of the notification.
|
||||||
|
* @param actions The actions to add to the notification.
|
||||||
|
* @return The notification that was sent.
|
||||||
|
*/
|
||||||
|
public static function infoWithActions(state:ChartEditorState, title:String, body:String, actions:Array<NotificationAction>):Notification
|
||||||
|
{
|
||||||
|
return sendNotification(title, body, NotificationType.Info, actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all active notifications.
|
||||||
|
* @param state The current state of the chart editor.
|
||||||
|
*/
|
||||||
|
public static function clearNotifications(state:ChartEditorState):Void
|
||||||
|
{
|
||||||
|
NotificationManager.instance.clearNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear a specific notification.
|
||||||
|
* @param state The current state of the chart editor.
|
||||||
|
* @param notif The notification to clear.
|
||||||
|
*/
|
||||||
|
public static function clearNotification(state:ChartEditorState, notif:Notification):Void
|
||||||
|
{
|
||||||
|
NotificationManager.instance.removeNotification(notif);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function sendNotification(title:String, body:String, ?type:NotificationType, ?actions:Array<NotificationAction>):Notification
|
||||||
|
{
|
||||||
|
#if !mac
|
||||||
|
var actionNames:Array<String> = actions == null ? [] : actions.map(action -> action.text);
|
||||||
|
|
||||||
|
var notif = NotificationManager.instance.addNotification(
|
||||||
|
{
|
||||||
|
title: title,
|
||||||
|
body: body,
|
||||||
|
type: type ?? NotificationType.Default,
|
||||||
|
expiryMs: Constants.NOTIFICATION_DISMISS_TIME,
|
||||||
|
actions: actionNames
|
||||||
|
});
|
||||||
|
|
||||||
|
if (actionNames.length > 0)
|
||||||
|
{
|
||||||
|
// TODO: Tell Ian that this is REALLY dumb.
|
||||||
|
var actionsContainer:HBox = notif.findComponent('actionsContainer', HBox);
|
||||||
|
actionsContainer.walkComponents(function(component) {
|
||||||
|
if (Std.isOfType(component, Button))
|
||||||
|
{
|
||||||
|
var button:Button = cast component;
|
||||||
|
var action:Null<NotificationAction> = actions.find(action -> action.text == button.text);
|
||||||
|
if (action != null && action.callback != null)
|
||||||
|
{
|
||||||
|
button.onClick = function(_) {
|
||||||
|
action.callback();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true; // Continue walking.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return notif;
|
||||||
|
#else
|
||||||
|
trace('WARNING: Notifications are not supported on Mac OS.');
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef NotificationAction =
|
||||||
|
{
|
||||||
|
text:String,
|
||||||
|
callback:Void->Void
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ package funkin.ui.debug.charting;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorAudioHandler;
|
using funkin.ui.debug.charting.handlers.ChartEditorAudioHandler;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorDialogHandler;
|
using funkin.ui.debug.charting.handlers.ChartEditorDialogHandler;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorImportExportHandler;
|
using funkin.ui.debug.charting.handlers.ChartEditorImportExportHandler;
|
||||||
|
using funkin.ui.debug.charting.handlers.ChartEditorNotificationHandler;
|
||||||
|
using funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorThemeHandler;
|
using funkin.ui.debug.charting.handlers.ChartEditorThemeHandler;
|
||||||
using funkin.ui.debug.charting.handlers.ChartEditorToolboxHandler;
|
using funkin.ui.debug.charting.handlers.ChartEditorToolboxHandler;
|
||||||
#end
|
#end
|
||||||
|
|
|
@ -96,7 +96,7 @@ class CLIUtil
|
||||||
|
|
||||||
static function printUsage():Void
|
static function printUsage():Void
|
||||||
{
|
{
|
||||||
trace('Usage: Funkin.exe [--chart <chart>]');
|
trace('Usage: Funkin.exe [--chart <chart>] [--help] [--version]');
|
||||||
}
|
}
|
||||||
|
|
||||||
static function buildDefaultParams():CLIParams
|
static function buildDefaultParams():CLIParams
|
||||||
|
|
|
@ -12,4 +12,11 @@ class DateUtil
|
||||||
return
|
return
|
||||||
'${date.getFullYear()}-${Std.string(date.getMonth() + 1).lpad('0', 2)}-${Std.string(date.getDate()).lpad('0', 2)}-${Std.string(date.getHours()).lpad('0', 2)}-${Std.string(date.getMinutes()).lpad('0', 2)}-${Std.string(date.getSeconds()).lpad('0', 2)}';
|
'${date.getFullYear()}-${Std.string(date.getMonth() + 1).lpad('0', 2)}-${Std.string(date.getDate()).lpad('0', 2)}-${Std.string(date.getHours()).lpad('0', 2)}-${Std.string(date.getMinutes()).lpad('0', 2)}-${Std.string(date.getSeconds()).lpad('0', 2)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function generateCleanTimestamp(?date:Date = null):String
|
||||||
|
{
|
||||||
|
if (date == null) date = Date.now();
|
||||||
|
|
||||||
|
return '${DateTools.format(date, '%B %d, %Y')} at ${DateTools.format(date, '%I:%M %p')}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package funkin.util;
|
||||||
|
|
||||||
import flixel.util.FlxSignal.FlxTypedSignal;
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
|
|
||||||
|
using StringTools;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for operating on the current window, such as changing the title.
|
* Utilities for operating on the current window, such as changing the title.
|
||||||
*/
|
*/
|
||||||
|
@ -18,7 +20,7 @@ class WindowUtil
|
||||||
* Runs platform-specific code to open a URL in a web browser.
|
* Runs platform-specific code to open a URL in a web browser.
|
||||||
* @param targetUrl The URL to open.
|
* @param targetUrl The URL to open.
|
||||||
*/
|
*/
|
||||||
public static function openURL(targetUrl:String)
|
public static function openURL(targetUrl:String):Void
|
||||||
{
|
{
|
||||||
#if CAN_OPEN_LINKS
|
#if CAN_OPEN_LINKS
|
||||||
#if linux
|
#if linux
|
||||||
|
@ -32,6 +34,45 @@ class WindowUtil
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs platform-specific code to open a path in the file explorer.
|
||||||
|
* @param targetPath The path to open.
|
||||||
|
*/
|
||||||
|
public static function openFolder(targetPath:String):Void
|
||||||
|
{
|
||||||
|
#if CAN_OPEN_LINKS
|
||||||
|
#if windows
|
||||||
|
Sys.command('explorer', [targetPath.replace("/", "\\")]);
|
||||||
|
#elseif mac
|
||||||
|
Sys.command('open', [targetPath]);
|
||||||
|
#elseif linux
|
||||||
|
Sys.command('open', [targetPath]);
|
||||||
|
#end
|
||||||
|
#else
|
||||||
|
throw 'Cannot open URLs on this platform.';
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs platform-specific code to open a file explorer and select a specific file.
|
||||||
|
* @param targetPath The path of the file to select.
|
||||||
|
*/
|
||||||
|
public static function openSelectFile(targetPath:String):Void
|
||||||
|
{
|
||||||
|
#if CAN_OPEN_LINKS
|
||||||
|
#if windows
|
||||||
|
Sys.command('explorer', ["/select," + targetPath.replace("/", "\\")]);
|
||||||
|
#elseif mac
|
||||||
|
Sys.command('open', ["-R", targetPath]);
|
||||||
|
#elseif linux
|
||||||
|
// TODO: unsure of the linux equivalent to opening a folder and then "selecting" a file.
|
||||||
|
Sys.command('open', [targetPath]);
|
||||||
|
#end
|
||||||
|
#else
|
||||||
|
throw 'Cannot open URLs on this platform.';
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatched when the game window is closed.
|
* Dispatched when the game window is closed.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,6 +2,7 @@ package funkin.util.logging;
|
||||||
|
|
||||||
import openfl.Lib;
|
import openfl.Lib;
|
||||||
import openfl.events.UncaughtErrorEvent;
|
import openfl.events.UncaughtErrorEvent;
|
||||||
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom crash handler that writes to a log file and displays a message box.
|
* A custom crash handler that writes to a log file and displays a message box.
|
||||||
|
@ -11,6 +12,19 @@ class CrashHandler
|
||||||
{
|
{
|
||||||
public static final LOG_FOLDER = 'logs';
|
public static final LOG_FOLDER = 'logs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before exiting the game when a standard error occurs, like a thrown exception.
|
||||||
|
* @param message The error message.
|
||||||
|
*/
|
||||||
|
public static var errorSignal(default, null):FlxTypedSignal<String->Void> = new FlxTypedSignal<String->Void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before exiting the game when a critical error occurs, like a stack overflow or null object reference.
|
||||||
|
* CAREFUL: The game may be in an unstable state when this is called.
|
||||||
|
* @param message The error message.
|
||||||
|
*/
|
||||||
|
public static var criticalErrorSignal(default, null):FlxTypedSignal<String->Void> = new FlxTypedSignal<String->Void>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes
|
* Initializes
|
||||||
*/
|
*/
|
||||||
|
@ -34,6 +48,8 @@ class CrashHandler
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
errorSignal.dispatch(generateErrorMessage(error));
|
||||||
|
|
||||||
#if sys
|
#if sys
|
||||||
logError(error);
|
logError(error);
|
||||||
#end
|
#end
|
||||||
|
@ -50,6 +66,8 @@ class CrashHandler
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
criticalErrorSignal.dispatch(message);
|
||||||
|
|
||||||
#if sys
|
#if sys
|
||||||
logErrorMessage(message, true);
|
logErrorMessage(message, true);
|
||||||
#end
|
#end
|
||||||
|
|
Loading…
Reference in a new issue