1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2025-11-23 05:33:48 +00:00

Fixes for quicksave

This commit is contained in:
EliteMasterEric 2023-10-24 15:50:02 -04:00
parent 818ae6ddd9
commit 33a1b81737
6 changed files with 238 additions and 63 deletions

2
assets

@ -1 +1 @@
Subproject commit fd745fcb16c6a0de73449ae833ce1d92f022d9d6 Subproject commit 8e8aeb06472ca294c569818cbefb1bb3dfce7854

View file

@ -218,6 +218,18 @@ class ChartEditorAudioHandler
snd.play(); snd.play();
} }
public static function wipeInstrumentalData(state:ChartEditorState):Void
{
state.audioInstTrackData.clear();
stopExistingInstrumental(state);
}
public static function wipeVocalData(state:ChartEditorState):Void
{
state.audioVocalTrackData.clear();
stopExistingVocals(state);
}
/** /**
* Convert byte data into a playable sound. * Convert byte data into a playable sound.
* *
@ -238,18 +250,27 @@ class ChartEditorAudioHandler
{ {
var zipEntries = []; var zipEntries = [];
for (key in state.audioInstTrackData.keys()) var instTrackIds = state.audioInstTrackData.keys().array();
for (key in instTrackIds)
{ {
if (key == 'default') if (key == 'default')
{ {
var data:Null<Bytes> = state.audioInstTrackData.get('default'); var data:Null<Bytes> = state.audioInstTrackData.get('default');
if (data == null) continue; if (data == null)
{
trace('[WARN] Failed to access inst track ($key)');
continue;
}
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst.ogg', data)); zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst.ogg', data));
} }
else else
{ {
var data:Null<Bytes> = state.audioInstTrackData.get(key); var data:Null<Bytes> = state.audioInstTrackData.get(key);
if (data == null) continue; if (data == null)
{
trace('[WARN] Failed to access inst track ($key)');
continue;
}
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst-${key}.ogg', data)); zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst-${key}.ogg', data));
} }
} }
@ -261,10 +282,15 @@ class ChartEditorAudioHandler
{ {
var zipEntries = []; var zipEntries = [];
var vocalTrackIds = state.audioVocalTrackData.keys().array();
for (key in state.audioVocalTrackData.keys()) for (key in state.audioVocalTrackData.keys())
{ {
var data:Null<Bytes> = state.audioVocalTrackData.get(key); var data:Null<Bytes> = state.audioVocalTrackData.get(key);
if (data == null) continue; if (data == null)
{
trace('[WARN] Failed to access vocal track ($key)');
continue;
}
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Voices-${key}.ogg', data)); zipEntries.push(FileUtil.makeZIPEntryFromBytes('Voices-${key}.ogg', data));
} }

View file

@ -106,7 +106,31 @@ class ChartEditorDialogHandler
state.stopWelcomeMusic(); state.stopWelcomeMusic();
// Load chart from file // Load chart from file
ChartEditorImportExportHandler.loadFromFNFCPath(state, chartPath); var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(state, chartPath);
if (result != null)
{
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Success',
body: result.length == 0 ? 'Loaded chart (${chartPath.toString()})' : 'Loaded chart (${chartPath.toString()})\n${result.join("\n")}',
type: result.length == 0 ? NotificationType.Success : NotificationType.Warning,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
#end
}
else
{
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Failure',
body: 'Failed to load chart (${chartPath.toString()})',
type: NotificationType.Error,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
#end
}
} }
if (!FileUtil.doesFileExist(chartPath)) if (!FileUtil.doesFileExist(chartPath))
@ -260,7 +284,8 @@ class ChartEditorDialogHandler
{ {
try try
{ {
if (ChartEditorImportExportHandler.loadFromFNFC(state, selectedFile.bytes)) var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFC(state, selectedFile.bytes);
if (result != null)
{ {
#if !mac #if !mac
NotificationManager.instance.addNotification( NotificationManager.instance.addNotification(
@ -299,21 +324,33 @@ class ChartEditorDialogHandler
try try
{ {
if (ChartEditorImportExportHandler.loadFromFNFCPath(state, path.toString())) var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(state, path.toString());
if (result != null)
{ {
#if !mac #if !mac
NotificationManager.instance.addNotification( NotificationManager.instance.addNotification(
{ {
title: 'Success', title: 'Success',
body: 'Loaded chart (${path.toString()})', body: result.length == 0 ? 'Loaded chart (${path.toString()})' : 'Loaded chart (${path.toString()})\n${result.join("\n")}',
type: NotificationType.Success, type: result.length == 0 ? NotificationType.Success : NotificationType.Warning,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
}); });
#end #end
dialog.hideDialog(DialogButton.APPLY); dialog.hideDialog(DialogButton.APPLY);
removeDropHandler(onDropFile); removeDropHandler(onDropFile);
} }
else
{
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Failure',
body: 'Failed to load chart (${path.toString()})',
type: NotificationType.Error,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
#end
}
} }
catch (err) catch (err)
{ {
@ -359,6 +396,8 @@ class ChartEditorDialogHandler
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
uploadVocalsDialog.onDialogClosed = function(_event) { uploadVocalsDialog.onDialogClosed = function(_event) {
state.isHaxeUIDialogOpen = false; state.isHaxeUIDialogOpen = false;
state.currentWorkingFilePath = null; // Built from parts, so no .fnfc to save to.
state.switchToCurrentInstrumental();
state.postLoadInstrumental(); state.postLoadInstrumental();
} }
} }
@ -398,6 +437,8 @@ class ChartEditorDialogHandler
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
uploadVocalsDialog.onDialogClosed = function(_event) { uploadVocalsDialog.onDialogClosed = function(_event) {
state.isHaxeUIDialogOpen = false; state.isHaxeUIDialogOpen = false;
state.currentWorkingFilePath = null; // New file, so no path.
state.switchToCurrentInstrumental();
state.postLoadInstrumental(); state.postLoadInstrumental();
} }
} }
@ -848,6 +889,7 @@ class ChartEditorDialogHandler
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button); var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Song Metadata dialog'; if (dialogContinue == null) throw 'Could not locate dialogContinue button in Song Metadata dialog';
dialogContinue.onClick = (_event) -> { dialogContinue.onClick = (_event) -> {
state.songMetadata.clear();
state.songMetadata.set(targetVariation, newSongMetadata); state.songMetadata.set(targetVariation, newSongMetadata);
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges); Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);

View file

@ -7,7 +7,7 @@ import haxe.io.Path;
import funkin.util.SerializerUtil; import funkin.util.SerializerUtil;
import haxe.ui.notifications.NotificationManager; import haxe.ui.notifications.NotificationManager;
import funkin.util.FileUtil; import funkin.util.FileUtil;
import funkin.util.FileUtil; import funkin.util.FileUtil.FileWriteMode;
import haxe.io.Bytes; import haxe.io.Bytes;
import funkin.play.song.Song; import funkin.play.song.Song;
import funkin.data.song.SongData.SongChartData; import funkin.data.song.SongData.SongChartData;
@ -53,7 +53,8 @@ class ChartEditorImportExportHandler
state.sortChartData(); state.sortChartData();
state.clearVocals(); ChartEditorAudioHandler.wipeInstrumentalData(state);
ChartEditorAudioHandler.wipeVocalData(state);
var variations:Array<String> = state.availableVariations; var variations:Array<String> = state.availableVariations;
for (variation in variations) for (variation in variations)
@ -91,7 +92,10 @@ class ChartEditorImportExportHandler
} }
} }
state.isHaxeUIDialogOpen = false;
state.currentWorkingFilePath = null; // New file, so no path.
state.switchToCurrentInstrumental(); state.switchToCurrentInstrumental();
state.postLoadInstrumental();
state.refreshMetadataToolbox(); state.refreshMetadataToolbox();
@ -138,31 +142,40 @@ class ChartEditorImportExportHandler
} }
} }
public static function loadFromFNFCPath(state:ChartEditorState, path:String):Bool /**
* Load a chart's metadata, chart data, and audio from an FNFC file path.
* @param state
* @param path
* @return `null` on failure, `[]` on success, `[warnings]` on success with warnings.
*/
public static function loadFromFNFCPath(state:ChartEditorState, path:String):Null<Array<String>>
{ {
var bytes:Null<Bytes> = FileUtil.readBytesFromPath(path); var bytes:Null<Bytes> = FileUtil.readBytesFromPath(path);
if (bytes == null) return false; if (bytes == null) return null;
trace('Loaded ${bytes.length} bytes from $path'); trace('Loaded ${bytes.length} bytes from $path');
var result:Bool = loadFromFNFC(state, bytes); var result:Null<Array<String>> = loadFromFNFC(state, bytes);
if (result) if (result != null)
{ {
state.currentWorkingFilePath = path; state.currentWorkingFilePath = path;
state.saveDataDirty = false; // Just loaded file!
} }
return result; return result;
} }
/** /**
* Load a chart's metadata, chart data, and audio from an FNFC archive.. * Load a chart's metadata, chart data, and audio from an FNFC archive.
* @param state * @param state
* @param bytes * @param bytes
* @param instId * @param instId
* @return Bool * @return `null` on failure, `[]` on success, `[warnings]` on success with warnings.
*/ */
public static function loadFromFNFC(state:ChartEditorState, bytes:Bytes):Bool public static function loadFromFNFC(state:ChartEditorState, bytes:Bytes):Null<Array<String>>
{ {
var warnings:Array<String> = [];
var songMetadatas:Map<String, SongMetadata> = []; var songMetadatas:Map<String, SongMetadata> = [];
var songChartDatas:Map<String, SongChartData> = []; var songChartDatas:Map<String, SongChartData> = [];
@ -231,8 +244,8 @@ class ChartEditorImportExportHandler
songChartDatas.set(variation, variChartData); songChartDatas.set(variation, variChartData);
} }
ChartEditorAudioHandler.stopExistingInstrumental(state); ChartEditorAudioHandler.wipeInstrumentalData(state);
ChartEditorAudioHandler.stopExistingVocals(state); ChartEditorAudioHandler.wipeVocalData(state);
// Load instrumentals // Load instrumentals
for (variation in [Constants.DEFAULT_VARIATION].concat(variationList)) for (variation in [Constants.DEFAULT_VARIATION].concat(variationList))
@ -264,12 +277,14 @@ class ChartEditorImportExportHandler
{ {
if (!ChartEditorAudioHandler.loadVocalsFromBytes(state, playerVocalsFileBytes, playerCharId, instId)) if (!ChartEditorAudioHandler.loadVocalsFromBytes(state, playerVocalsFileBytes, playerCharId, instId))
{ {
throw 'Could not load vocals ($playerCharId).'; warnings.push('Could not parse vocals ($playerCharId).');
// throw 'Could not parse vocals ($playerCharId).';
} }
} }
else else
{ {
throw 'Could not find vocals ($playerVocalsFileName).'; warnings.push('Could not find vocals ($playerVocalsFileName).');
// throw 'Could not find vocals ($playerVocalsFileName).';
} }
if (opponentCharId != null) if (opponentCharId != null)
@ -280,12 +295,14 @@ class ChartEditorImportExportHandler
{ {
if (!ChartEditorAudioHandler.loadVocalsFromBytes(state, opponentVocalsFileBytes, opponentCharId, instId)) if (!ChartEditorAudioHandler.loadVocalsFromBytes(state, opponentVocalsFileBytes, opponentCharId, instId))
{ {
throw 'Could not load vocals ($opponentCharId).'; warnings.push('Could not parse vocals ($opponentCharId).');
// throw 'Could not parse vocals ($opponentCharId).';
} }
} }
else else
{ {
throw 'Could not load vocals ($playerCharId-$instId).'; warnings.push('Could not find vocals ($opponentVocalsFileName).');
// throw 'Could not find vocals ($opponentVocalsFileName).';
} }
} }
} }
@ -297,7 +314,7 @@ class ChartEditorImportExportHandler
state.switchToCurrentInstrumental(); state.switchToCurrentInstrumental();
return true; return warnings;
} }
/** /**
@ -345,8 +362,10 @@ class ChartEditorImportExportHandler
if (force) if (force)
{ {
var targetMode:FileWriteMode = Force;
if (targetPath == null) if (targetPath == null)
{ {
targetMode = Skip;
targetPath = Path.join([ targetPath = Path.join([
'./backups/', './backups/',
'chart-editor-${DateUtil.generateTimestamp()}.${Constants.EXT_CHART}' 'chart-editor-${DateUtil.generateTimestamp()}.${Constants.EXT_CHART}'
@ -355,7 +374,8 @@ class ChartEditorImportExportHandler
// 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); FileUtil.saveFilesAsZIPToPath(zipEntries, targetPath, targetMode);
state.saveDataDirty = false;
} }
else else
{ {
@ -364,9 +384,11 @@ class ChartEditorImportExportHandler
if (paths.length != 1) if (paths.length != 1)
{ {
trace('[WARN] Could not get save path.'); trace('[WARN] Could not get save path.');
state.applyWindowTitle();
} }
else else
{ {
trace('Saved to "${paths[0]}"');
state.currentWorkingFilePath = paths[0]; state.currentWorkingFilePath = paths[0];
state.applyWindowTitle(); state.applyWindowTitle();
} }
@ -380,6 +402,7 @@ class ChartEditorImportExportHandler
try try
{ {
FileUtil.saveChartAsFNFC(zipEntries, onSave, onCancel, '${state.currentSongId}.${Constants.EXT_CHART}'); FileUtil.saveChartAsFNFC(zipEntries, onSave, onCancel, '${state.currentSongId}.${Constants.EXT_CHART}');
state.saveDataDirty = false;
} }
catch (e) {} catch (e) {}
} }

View file

@ -644,7 +644,9 @@ class ChartEditorState extends HaxeUIState
} }
} }
return saveDataDirty = value; saveDataDirty = value;
applyWindowTitle();
return saveDataDirty;
} }
/** /**
@ -882,7 +884,7 @@ class ChartEditorState extends HaxeUIState
var result:Null<SongMetadata> = songMetadata.get(selectedVariation); var result:Null<SongMetadata> = songMetadata.get(selectedVariation);
if (result == null) if (result == null)
{ {
result = new SongMetadata('Dad Battle', 'Kawai Sprite', selectedVariation); result = new SongMetadata('DadBattle', 'Kawai Sprite', selectedVariation);
songMetadata.set(selectedVariation, result); songMetadata.set(selectedVariation, result);
} }
return result; return result;
@ -1170,7 +1172,7 @@ class ChartEditorState extends HaxeUIState
/** /**
* The item in the menubar to save the currently opened chart. * The item in the menubar to save the currently opened chart.
*/ */
var menubarItemSave:Null<FunkinMenuItem> = null; var menubarItemSaveChart:Null<FunkinMenuItem> = null;
/** /**
* The playbar head slider. * The playbar head slider.
@ -1229,10 +1231,11 @@ class ChartEditorState extends HaxeUIState
/** /**
* A list of previous working file paths. * A list of previous working file paths.
* Also known as the "recent files" list. * Also known as the "recent files" list.
* The first element is [null] if the current working file has not been saved anywhere yet.
*/ */
public var previousWorkingFilePaths(default, set):Array<String> = []; public var previousWorkingFilePaths(default, set):Array<Null<String>> = [null];
function set_previousWorkingFilePaths(value:Array<String>):Array<String> function set_previousWorkingFilePaths(value:Array<Null<String>>):Array<Null<String>>
{ {
// Called only when the WHOLE LIST is overridden. // Called only when the WHOLE LIST is overridden.
previousWorkingFilePaths = value; previousWorkingFilePaths = value;
@ -1260,7 +1263,9 @@ class ChartEditorState extends HaxeUIState
if (previousWorkingFilePaths.contains(null)) if (previousWorkingFilePaths.contains(null))
{ {
// Filter all instances of `null` from the array. // Filter all instances of `null` from the array.
previousWorkingFilePaths = previousWorkingFilePaths.filter((x) -> x != null); previousWorkingFilePaths = previousWorkingFilePaths.filter(function(x:Null<String>):Bool {
return x != null;
});
} }
if (previousWorkingFilePaths.contains(value)) if (previousWorkingFilePaths.contains(value))
@ -1340,22 +1345,31 @@ class ChartEditorState extends HaxeUIState
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!
if (ChartEditorImportExportHandler.loadFromFNFCPath(this, params.fnfcTargetPath)) var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(this, params.fnfcTargetPath);
if (result != null)
{ {
// Don't open the welcome dialog!
#if !mac #if !mac
NotificationManager.instance.addNotification( NotificationManager.instance.addNotification(
{ {
title: 'Success', title: 'Success',
body: 'Loaded chart (${params.fnfcTargetPath})', body: result.length == 0 ? 'Loaded chart (${params.fnfcTargetPath})' : 'Loaded chart (${params.fnfcTargetPath})\n${result.join("\n")}',
type: NotificationType.Success, type: result.length == 0 ? NotificationType.Success : NotificationType.Warning,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
}); });
#end #end
} }
else else
{ {
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Failure',
body: 'Failed to load chart (${params.fnfcTargetPath})',
type: NotificationType.Error,
expiryMs: ChartEditorState.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); ChartEditorDialogHandler.openWelcomeDialog(this, false);
} }
@ -1376,7 +1390,14 @@ class ChartEditorState extends HaxeUIState
{ {
var save:Save = Save.get(); var save:Save = Save.get();
previousWorkingFilePaths = save.chartEditorPreviousFiles; if (previousWorkingFilePaths[0] == null)
{
previousWorkingFilePaths = [null].concat(save.chartEditorPreviousFiles);
}
else
{
previousWorkingFilePaths = [currentWorkingFilePath].concat(save.chartEditorPreviousFiles);
}
noteSnapQuantIndex = save.chartEditorNoteQuant; noteSnapQuantIndex = save.chartEditorNoteQuant;
currentLiveInputStyle = save.chartEditorLiveInputStyle; currentLiveInputStyle = save.chartEditorLiveInputStyle;
isViewDownscroll = save.chartEditorDownscroll; isViewDownscroll = save.chartEditorDownscroll;
@ -1396,7 +1417,12 @@ class ChartEditorState extends HaxeUIState
{ {
var save:Save = Save.get(); var save:Save = Save.get();
save.chartEditorPreviousFiles = previousWorkingFilePaths; // Can't use filter() because of null safety checking!
var filteredWorkingFilePaths:Array<String> = [];
for (chartPath in previousWorkingFilePaths)
if (chartPath != null) filteredWorkingFilePaths.push(chartPath);
save.chartEditorPreviousFiles = filteredWorkingFilePaths;
save.chartEditorNoteQuant = noteSnapQuantIndex; save.chartEditorNoteQuant = noteSnapQuantIndex;
save.chartEditorLiveInputStyle = currentLiveInputStyle; save.chartEditorLiveInputStyle = currentLiveInputStyle;
save.chartEditorDownscroll = isViewDownscroll; save.chartEditorDownscroll = isViewDownscroll;
@ -1428,7 +1454,31 @@ class ChartEditorState extends HaxeUIState
stopWelcomeMusic(); stopWelcomeMusic();
// Load chart from file // Load chart from file
ChartEditorImportExportHandler.loadFromFNFCPath(this, chartPath); var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(this, chartPath);
if (result != null)
{
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Success',
body: result.length == 0 ? 'Loaded chart (${chartPath.toString()})' : 'Loaded chart (${chartPath.toString()})\n${result.join("\n")}',
type: result.length == 0 ? NotificationType.Success : NotificationType.Warning,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
#end
}
else
{
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Failure',
body: 'Failed to load chart (${chartPath.toString()})',
type: NotificationType.Error,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
#end
}
} }
if (!FileUtil.doesFileExist(chartPath)) if (!FileUtil.doesFileExist(chartPath))
@ -1774,8 +1824,8 @@ class ChartEditorState extends HaxeUIState
menubarOpenRecent = findComponent('menubarOpenRecent', Menu); menubarOpenRecent = findComponent('menubarOpenRecent', Menu);
if (menubarOpenRecent == null) throw "Could not find menubarOpenRecent!"; if (menubarOpenRecent == null) throw "Could not find menubarOpenRecent!";
menubarItemSave = findComponent('menubarItemSave', FunkinMenuItem); menubarItemSaveChart = findComponent('menubarItemSaveChart', FunkinMenuItem);
if (menubarItemSave == null) throw "Could not find menubarItemSave!"; if (menubarItemSaveChart == null) throw "Could not find menubarItemSaveChart!";
// Setup notifications. // Setup notifications.
@:privateAccess @:privateAccess
@ -3340,12 +3390,24 @@ class ChartEditorState extends HaxeUIState
ChartEditorDialogHandler.openBrowseFNFC(this, true); ChartEditorDialogHandler.openBrowseFNFC(this, true);
} }
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.S)
{
if (currentWorkingFilePath == null || FlxG.keys.pressed.SHIFT)
{
// CTRL + SHIFT + S = Save As // CTRL + SHIFT + S = Save As
ChartEditorImportExportHandler.exportAllSongData(this, false);
}
else
{
// CTRL + S = Save Chart
ChartEditorImportExportHandler.exportAllSongData(this, true, currentWorkingFilePath);
}
}
if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.S) if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.S)
{ {
ChartEditorImportExportHandler.exportAllSongData(this, false); ChartEditorImportExportHandler.exportAllSongData(this, 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)
{ {
@ -3361,6 +3423,8 @@ class ChartEditorState extends HaxeUIState
// TODO: PR Flixel to make onComplete nullable. // TODO: PR Flixel to make onComplete nullable.
if (audioInstTrack != null) audioInstTrack.onComplete = null; if (audioInstTrack != null) audioInstTrack.onComplete = null;
FlxG.switchState(new MainMenuState()); FlxG.switchState(new MainMenuState());
resetWindowTitle();
} }
/** /**
@ -4534,22 +4598,36 @@ class ChartEditorState extends HaxeUIState
function applyCanQuickSave():Void function applyCanQuickSave():Void
{ {
if (currentWorkingFilePath == null) {} if (menubarItemSaveChart == null) return;
else {}
if (currentWorkingFilePath == null)
{
menubarItemSaveChart.disabled = true;
}
else
{
menubarItemSaveChart.disabled = false;
}
} }
function applyWindowTitle():Void function applyWindowTitle():Void
{ {
var inner:String = (currentSongMetadata.songName != null) ? currentSongMetadata.songName : 'Untitled'; var inner:String = 'New Chart';
if (currentWorkingFilePath == null) var cwfp:Null<String> = currentWorkingFilePath;
if (cwfp != null)
{
inner = cwfp;
}
if (currentWorkingFilePath == null || saveDataDirty)
{ {
inner += '*'; inner += '*';
} }
else WindowUtil.setWindowTitle('Friday Night Funkin\' Chart Editor - ${inner}');
{
inner += ' (${currentWorkingFilePath})';
} }
WindowUtil.setWindowTitle('FNF Chart Editor - ${inner}');
function resetWindowTitle():Void
{
WindowUtil.setWindowTitle('Friday Night Funkin\'');
} }
} }

View file

@ -101,7 +101,7 @@ class FileUtil
} }
/** /**
* Browses for a file location to save to, then calls `onSelect(path)` when a path chosen. * Browses for a file location to save to, then calls `onSave(path)` when a path chosen.
* Note that on HTML5 you can't do much with this, you should call `saveFile(resource:haxe.io.Bytes)` instead. * Note that on HTML5 you can't do much with this, you should call `saveFile(resource:haxe.io.Bytes)` instead.
* *
* @param typeFilter TODO What does this do? * @param typeFilter TODO What does this do?
@ -183,7 +183,7 @@ class FileUtil
var filter:String = convertTypeFilter(typeFilter); var filter:String = convertTypeFilter(typeFilter);
var fileDialog:FileDialog = new FileDialog(); var fileDialog:FileDialog = new FileDialog();
if (onSave != null) fileDialog.onSelect.add(onSave); if (onSave != null) fileDialog.onSave.add(onSave);
if (onCancel != null) fileDialog.onCancel.add(onCancel); if (onCancel != null) fileDialog.onCancel.add(onCancel);
fileDialog.save(data, filter, defaultFileName, dialogTitle); fileDialog.save(data, filter, defaultFileName, dialogTitle);
@ -268,7 +268,8 @@ class FileUtil
var zipBytes:Bytes = createZIPFromEntries(resources); var zipBytes:Bytes = createZIPFromEntries(resources);
var onSave:String->Void = function(path:String) { var onSave:String->Void = function(path:String) {
onSave([path]); trace('Saved ${resources.length} files to ZIP at "$path".');
if (onSave != null) onSave([path]);
}; };
// Prompt the user to save the ZIP file. // Prompt the user to save the ZIP file.
@ -287,7 +288,8 @@ class FileUtil
var zipBytes:Bytes = createZIPFromEntries(resources); var zipBytes:Bytes = createZIPFromEntries(resources);
var onSave:String->Void = function(path:String) { var onSave:String->Void = function(path:String) {
onSave([path]); trace('Saved FNF file to "$path"');
if (onSave != null) onSave([path]);
}; };
// Prompt the user to save the ZIP file. // Prompt the user to save the ZIP file.
@ -302,14 +304,14 @@ class FileUtil
* Use `saveFilesAsZIP` instead. * Use `saveFilesAsZIP` instead.
* @param force Whether to force overwrite an existing file. * @param force Whether to force overwrite an existing file.
*/ */
public static function saveFilesAsZIPToPath(resources:Array<Entry>, path:String, force:Bool = false):Bool public static function saveFilesAsZIPToPath(resources:Array<Entry>, path:String, mode:FileWriteMode = Skip):Bool
{ {
#if desktop #if desktop
// Create a ZIP file. // Create a ZIP file.
var zipBytes:Bytes = createZIPFromEntries(resources); var zipBytes:Bytes = createZIPFromEntries(resources);
// Write the ZIP. // Write the ZIP.
writeBytesToPath(path, zipBytes, force ? Force : Skip); writeBytesToPath(path, zipBytes, mode);
return true; return true;
#else #else
@ -449,12 +451,14 @@ class FileUtil
} }
else else
{ {
throw 'File already exists: $path'; // Do nothing.
// throw 'File already exists: $path';
} }
case Ask: case Ask:
if (doesFileExist(path)) if (doesFileExist(path))
{ {
// TODO: We don't have the technology to use native popups yet. // TODO: We don't have the technology to use native popups yet.
throw 'File already exists: $path';
} }
else else
{ {
@ -490,12 +494,14 @@ class FileUtil
} }
else else
{ {
throw 'File already exists: $path'; // Do nothing.
// throw 'File already exists: $path';
} }
case Ask: case Ask:
if (doesFileExist(path)) if (doesFileExist(path))
{ {
// TODO: We don't have the technology to use native popups yet. // TODO: We don't have the technology to use native popups yet.
throw 'File already exists: $path';
} }
else else
{ {