mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-11-10 00:34:40 +00:00
Merge pull request #200 from FunkinCrew/feature/chart-editor-open-recent
Chart Editor: Open Recent and Save Preferences
This commit is contained in:
commit
a32bebb009
|
@ -5,12 +5,17 @@ import funkin.save.migrator.SaveDataMigrator;
|
||||||
import thx.semver.Version;
|
import thx.semver.Version;
|
||||||
import funkin.input.Controls.Device;
|
import funkin.input.Controls.Device;
|
||||||
import funkin.save.migrator.RawSaveData_v1_0_0;
|
import funkin.save.migrator.RawSaveData_v1_0_0;
|
||||||
|
import funkin.save.migrator.SaveDataMigrator;
|
||||||
|
import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
|
||||||
|
import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
|
||||||
|
import thx.semver.Version;
|
||||||
|
|
||||||
@:nullSafety
|
@:nullSafety
|
||||||
@:forward(volume, mute)
|
@:forward(volume, mute)
|
||||||
abstract Save(RawSaveData)
|
abstract Save(RawSaveData)
|
||||||
{
|
{
|
||||||
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.0";
|
// Version 2.0.1 adds attributes to `optionsChartEditor`, that should return default values if they are null.
|
||||||
|
public static final SAVE_DATA_VERSION:thx.semver.Version = "2.0.1";
|
||||||
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
|
public static final SAVE_DATA_VERSION_RULE:thx.semver.VersionRule = "2.0.x";
|
||||||
|
|
||||||
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
|
// We load this version's saves from a new save path, to maintain SOME level of backwards compatibility.
|
||||||
|
@ -94,6 +99,18 @@ abstract Save(RawSaveData)
|
||||||
optionsChartEditor:
|
optionsChartEditor:
|
||||||
{
|
{
|
||||||
// Reasonable defaults.
|
// Reasonable defaults.
|
||||||
|
previousFiles: [],
|
||||||
|
noteQuant: 3,
|
||||||
|
chartEditorLiveInputStyle: ChartEditorLiveInputStyle.None,
|
||||||
|
theme: ChartEditorTheme.Light,
|
||||||
|
playtestStartTime: false,
|
||||||
|
downscroll: false,
|
||||||
|
metronomeEnabled: true,
|
||||||
|
hitsoundsEnabledPlayer: true,
|
||||||
|
hitsoundsEnabledOpponent: true,
|
||||||
|
instVolume: 1.0,
|
||||||
|
voicesVolume: 1.0,
|
||||||
|
playbackSpeed: 1.0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -124,7 +141,9 @@ abstract Save(RawSaveData)
|
||||||
|
|
||||||
function set_ngSessionId(value:Null<String>):Null<String>
|
function set_ngSessionId(value:Null<String>):Null<String>
|
||||||
{
|
{
|
||||||
return this.api.newgrounds.sessionId = value;
|
this.api.newgrounds.sessionId = value;
|
||||||
|
flush();
|
||||||
|
return this.api.newgrounds.sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public var enabledModIds(get, set):Array<String>;
|
public var enabledModIds(get, set):Array<String>;
|
||||||
|
@ -136,7 +155,213 @@ abstract Save(RawSaveData)
|
||||||
|
|
||||||
function set_enabledModIds(value:Array<String>):Array<String>
|
function set_enabledModIds(value:Array<String>):Array<String>
|
||||||
{
|
{
|
||||||
return this.mods.enabledMods = value;
|
this.mods.enabledMods = value;
|
||||||
|
flush();
|
||||||
|
return this.mods.enabledMods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorPreviousFiles(get, set):Array<String>;
|
||||||
|
|
||||||
|
function get_chartEditorPreviousFiles():Array<String>
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.previousFiles == null) this.optionsChartEditor.previousFiles = [];
|
||||||
|
|
||||||
|
return this.optionsChartEditor.previousFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorPreviousFiles(value:Array<String>):Array<String>
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.previousFiles = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.previousFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorNoteQuant(get, set):Int;
|
||||||
|
|
||||||
|
function get_chartEditorNoteQuant():Int
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.noteQuant == null) this.optionsChartEditor.noteQuant = 3;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.noteQuant;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorNoteQuant(value:Int):Int
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.noteQuant = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.noteQuant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorLiveInputStyle(get, set):ChartEditorLiveInputStyle;
|
||||||
|
|
||||||
|
function get_chartEditorLiveInputStyle():ChartEditorLiveInputStyle
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.chartEditorLiveInputStyle == null) this.optionsChartEditor.chartEditorLiveInputStyle = ChartEditorLiveInputStyle.None;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.chartEditorLiveInputStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorLiveInputStyle(value:ChartEditorLiveInputStyle):ChartEditorLiveInputStyle
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.chartEditorLiveInputStyle = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.chartEditorLiveInputStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorDownscroll(get, set):Bool;
|
||||||
|
|
||||||
|
function get_chartEditorDownscroll():Bool
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.downscroll == null) this.optionsChartEditor.downscroll = false;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.downscroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorDownscroll(value:Bool):Bool
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.downscroll = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.downscroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorPlaytestStartTime(get, set):Bool;
|
||||||
|
|
||||||
|
function get_chartEditorPlaytestStartTime():Bool
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.playtestStartTime == null) this.optionsChartEditor.playtestStartTime = false;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.playtestStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorPlaytestStartTime(value:Bool):Bool
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.playtestStartTime = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.playtestStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorTheme(get, set):ChartEditorTheme;
|
||||||
|
|
||||||
|
function get_chartEditorTheme():ChartEditorTheme
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.theme == null) this.optionsChartEditor.theme = ChartEditorTheme.Light;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorTheme(value:ChartEditorTheme):ChartEditorTheme
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.theme = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorMetronomeEnabled(get, set):Bool;
|
||||||
|
|
||||||
|
function get_chartEditorMetronomeEnabled():Bool
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.metronomeEnabled == null) this.optionsChartEditor.metronomeEnabled = true;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.metronomeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorMetronomeEnabled(value:Bool):Bool
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.metronomeEnabled = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.metronomeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorHitsoundsEnabledPlayer(get, set):Bool;
|
||||||
|
|
||||||
|
function get_chartEditorHitsoundsEnabledPlayer():Bool
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.hitsoundsEnabledPlayer == null) this.optionsChartEditor.hitsoundsEnabledPlayer = true;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.hitsoundsEnabledPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorHitsoundsEnabledPlayer(value:Bool):Bool
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.hitsoundsEnabledPlayer = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.hitsoundsEnabledPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorHitsoundsEnabledOpponent(get, set):Bool;
|
||||||
|
|
||||||
|
function get_chartEditorHitsoundsEnabledOpponent():Bool
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.hitsoundsEnabledOpponent == null) this.optionsChartEditor.hitsoundsEnabledOpponent = true;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.hitsoundsEnabledOpponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorHitsoundsEnabledOpponent(value:Bool):Bool
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.hitsoundsEnabledOpponent = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.hitsoundsEnabledOpponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorInstVolume(get, set):Float;
|
||||||
|
|
||||||
|
function get_chartEditorInstVolume():Float
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.instVolume == null) this.optionsChartEditor.instVolume = 1.0;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.instVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorInstVolume(value:Float):Float
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.instVolume = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.instVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorVoicesVolume(get, set):Float;
|
||||||
|
|
||||||
|
function get_chartEditorVoicesVolume():Float
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.voicesVolume == null) this.optionsChartEditor.voicesVolume = 1.0;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.voicesVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorVoicesVolume(value:Float):Float
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.voicesVolume = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.voicesVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
public var chartEditorPlaybackSpeed(get, set):Float;
|
||||||
|
|
||||||
|
function get_chartEditorPlaybackSpeed():Float
|
||||||
|
{
|
||||||
|
if (this.optionsChartEditor.playbackSpeed == null) this.optionsChartEditor.playbackSpeed = 1.0;
|
||||||
|
|
||||||
|
return this.optionsChartEditor.playbackSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_chartEditorPlaybackSpeed(value:Float):Float
|
||||||
|
{
|
||||||
|
// Set and apply.
|
||||||
|
this.optionsChartEditor.playbackSpeed = value;
|
||||||
|
flush();
|
||||||
|
return this.optionsChartEditor.playbackSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -699,4 +924,77 @@ typedef SaveControlsData =
|
||||||
/**
|
/**
|
||||||
* An anonymous structure containing all the user's options and preferences, specific to the Chart Editor.
|
* An anonymous structure containing all the user's options and preferences, specific to the Chart Editor.
|
||||||
*/
|
*/
|
||||||
typedef SaveDataChartEditorOptions = {};
|
typedef SaveDataChartEditorOptions =
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Previous files opened in the Chart Editor.
|
||||||
|
* @default `[]`
|
||||||
|
*/
|
||||||
|
var ?previousFiles:Array<String>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note snapping level in the Chart Editor.
|
||||||
|
* @default `3`
|
||||||
|
*/
|
||||||
|
var ?noteQuant:Int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Live input style in the Chart Editor.
|
||||||
|
* @default `ChartEditorLiveInputStyle.None`
|
||||||
|
*/
|
||||||
|
var ?chartEditorLiveInputStyle:ChartEditorLiveInputStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme in the Chart Editor.
|
||||||
|
* @default `ChartEditorTheme.Light`
|
||||||
|
*/
|
||||||
|
var ?theme:ChartEditorTheme;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downscroll in the Chart Editor.
|
||||||
|
* @default `false`
|
||||||
|
*/
|
||||||
|
var ?downscroll:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metronome sounds in the Chart Editor.
|
||||||
|
* @default `true`
|
||||||
|
*/
|
||||||
|
var ?metronomeEnabled:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, playtest songs from the current position in the Chart Editor.
|
||||||
|
* @default `false`
|
||||||
|
*/
|
||||||
|
var ?playtestStartTime:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Player note hit sounds in the Chart Editor.
|
||||||
|
* @default `true`
|
||||||
|
*/
|
||||||
|
var ?hitsoundsEnabledPlayer:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opponent note hit sounds in the Chart Editor.
|
||||||
|
* @default `true`
|
||||||
|
*/
|
||||||
|
var ?hitsoundsEnabledOpponent:Bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumental volume in the Chart Editor.
|
||||||
|
* @default `1.0`
|
||||||
|
*/
|
||||||
|
var ?instVolume:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Voices volume in the Chart Editor.
|
||||||
|
* @default `1.0`
|
||||||
|
*/
|
||||||
|
var ?voicesVolume:Float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Playback speed in the Chart Editor.
|
||||||
|
* @default `1.0`
|
||||||
|
*/
|
||||||
|
var ?playbackSpeed:Float;
|
||||||
|
};
|
||||||
|
|
|
@ -12,6 +12,7 @@ import flixel.math.FlxMath;
|
||||||
import flixel.math.FlxPoint;
|
import flixel.math.FlxPoint;
|
||||||
import flixel.math.FlxRect;
|
import flixel.math.FlxRect;
|
||||||
import flixel.sound.FlxSound;
|
import flixel.sound.FlxSound;
|
||||||
|
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||||
import flixel.tweens.FlxEase;
|
import flixel.tweens.FlxEase;
|
||||||
import flixel.tweens.FlxTween;
|
import flixel.tweens.FlxTween;
|
||||||
import flixel.tweens.misc.VarTween;
|
import flixel.tweens.misc.VarTween;
|
||||||
|
@ -32,6 +33,7 @@ import funkin.input.TurboKeyHandler;
|
||||||
import funkin.modding.events.ScriptEvent;
|
import funkin.modding.events.ScriptEvent;
|
||||||
import funkin.play.character.BaseCharacter.CharacterType;
|
import funkin.play.character.BaseCharacter.CharacterType;
|
||||||
import funkin.play.character.CharacterData;
|
import funkin.play.character.CharacterData;
|
||||||
|
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||||
import funkin.play.components.HealthIcon;
|
import funkin.play.components.HealthIcon;
|
||||||
import funkin.play.notes.NoteSprite;
|
import funkin.play.notes.NoteSprite;
|
||||||
import funkin.play.PlayState;
|
import funkin.play.PlayState;
|
||||||
|
@ -46,6 +48,7 @@ import funkin.data.song.SongDataUtils;
|
||||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||||
import funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
|
import funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
|
||||||
import funkin.play.stage.StageData;
|
import funkin.play.stage.StageData;
|
||||||
|
import funkin.save.Save;
|
||||||
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
||||||
import funkin.ui.debug.charting.commands.AddNotesCommand;
|
import funkin.ui.debug.charting.commands.AddNotesCommand;
|
||||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||||
|
@ -77,6 +80,7 @@ import funkin.util.SortUtil;
|
||||||
import funkin.util.WindowUtil;
|
import funkin.util.WindowUtil;
|
||||||
import haxe.DynamicAccess;
|
import haxe.DynamicAccess;
|
||||||
import haxe.io.Bytes;
|
import haxe.io.Bytes;
|
||||||
|
import haxe.io.Path;
|
||||||
import haxe.ui.components.DropDown;
|
import haxe.ui.components.DropDown;
|
||||||
import haxe.ui.components.Label;
|
import haxe.ui.components.Label;
|
||||||
import haxe.ui.components.NumberStepper;
|
import haxe.ui.components.NumberStepper;
|
||||||
|
@ -84,6 +88,7 @@ import haxe.ui.components.Slider;
|
||||||
import haxe.ui.components.TextField;
|
import haxe.ui.components.TextField;
|
||||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||||
import haxe.ui.containers.Frame;
|
import haxe.ui.containers.Frame;
|
||||||
|
import haxe.ui.containers.menus.Menu;
|
||||||
import haxe.ui.containers.menus.MenuItem;
|
import haxe.ui.containers.menus.MenuItem;
|
||||||
import haxe.ui.containers.TreeView;
|
import haxe.ui.containers.TreeView;
|
||||||
import haxe.ui.containers.TreeViewNode;
|
import haxe.ui.containers.TreeViewNode;
|
||||||
|
@ -95,6 +100,7 @@ import haxe.ui.focus.FocusManager;
|
||||||
import haxe.ui.notifications.NotificationManager;
|
import haxe.ui.notifications.NotificationManager;
|
||||||
import haxe.ui.notifications.NotificationType;
|
import haxe.ui.notifications.NotificationType;
|
||||||
import openfl.display.BitmapData;
|
import openfl.display.BitmapData;
|
||||||
|
import funkin.util.FileUtil;
|
||||||
|
|
||||||
using Lambda;
|
using Lambda;
|
||||||
|
|
||||||
|
@ -750,7 +756,9 @@ class ChartEditorState extends HaxeUIState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return saveDataDirty = value;
|
saveDataDirty = value;
|
||||||
|
applyWindowTitle();
|
||||||
|
return saveDataDirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -932,7 +940,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;
|
||||||
|
@ -1272,6 +1280,16 @@ class ChartEditorState extends HaxeUIState
|
||||||
*/
|
*/
|
||||||
var playbarHeadLayout:Null<Component> = null;
|
var playbarHeadLayout:Null<Component> = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The submenu in the menubar containing recently opened files.
|
||||||
|
*/
|
||||||
|
var menubarOpenRecent:Null<Menu> = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item in the menubar to save the currently opened chart.
|
||||||
|
*/
|
||||||
|
var menubarItemSaveChart:Null<MenuItem> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The playbar head slider.
|
* The playbar head slider.
|
||||||
*/
|
*/
|
||||||
|
@ -1326,9 +1344,68 @@ class ChartEditorState extends HaxeUIState
|
||||||
var params:Null<ChartEditorParams>;
|
var params:Null<ChartEditorParams>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current file path which the chart editor is working with.
|
* A list of previous working file paths.
|
||||||
|
* 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 currentWorkingFilePath:Null<String>;
|
public var previousWorkingFilePaths(default, set):Array<Null<String>> = [null];
|
||||||
|
|
||||||
|
function set_previousWorkingFilePaths(value:Array<Null<String>>):Array<Null<String>>
|
||||||
|
{
|
||||||
|
// Called only when the WHOLE LIST is overridden.
|
||||||
|
previousWorkingFilePaths = value;
|
||||||
|
applyWindowTitle();
|
||||||
|
populateOpenRecentMenu();
|
||||||
|
applyCanQuickSave();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current file path which the chart editor is working with.
|
||||||
|
* If `null`, the current chart has not been saved yet.
|
||||||
|
*/
|
||||||
|
public var currentWorkingFilePath(get, set):Null<String>;
|
||||||
|
|
||||||
|
function get_currentWorkingFilePath():Null<String>
|
||||||
|
{
|
||||||
|
return previousWorkingFilePaths[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_currentWorkingFilePath(value:Null<String>):Null<String>
|
||||||
|
{
|
||||||
|
if (value == previousWorkingFilePaths[0]) return value;
|
||||||
|
|
||||||
|
if (previousWorkingFilePaths.contains(null))
|
||||||
|
{
|
||||||
|
// Filter all instances of `null` from the array.
|
||||||
|
previousWorkingFilePaths = previousWorkingFilePaths.filter(function(x:Null<String>):Bool {
|
||||||
|
return x != null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousWorkingFilePaths.contains(value))
|
||||||
|
{
|
||||||
|
// Move the path to the front of the list.
|
||||||
|
previousWorkingFilePaths.remove(value);
|
||||||
|
previousWorkingFilePaths.unshift(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Add the path to the front of the list.
|
||||||
|
previousWorkingFilePaths.unshift(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (previousWorkingFilePaths.length > Constants.MAX_PREVIOUS_WORKING_FILES)
|
||||||
|
{
|
||||||
|
// Remove the last path in the list.
|
||||||
|
previousWorkingFilePaths.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
populateOpenRecentMenu();
|
||||||
|
applyWindowTitle();
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public function new(?params:ChartEditorParams)
|
public function new(?params:ChartEditorParams)
|
||||||
{
|
{
|
||||||
|
@ -1386,6 +1463,8 @@ class ChartEditorState extends HaxeUIState
|
||||||
// Show the mouse cursor.
|
// Show the mouse cursor.
|
||||||
Cursor.show();
|
Cursor.show();
|
||||||
|
|
||||||
|
loadPreferences();
|
||||||
|
|
||||||
fixCamera();
|
fixCamera();
|
||||||
|
|
||||||
// Get rid of any music from the previous state.
|
// Get rid of any music from the previous state.
|
||||||
|
@ -1406,6 +1485,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
buildSelectionBox();
|
buildSelectionBox();
|
||||||
|
|
||||||
buildAdditionalUI();
|
buildAdditionalUI();
|
||||||
|
populateOpenRecentMenu();
|
||||||
ChartEditorShortcutHandler.applyPlatformShortcutText(this);
|
ChartEditorShortcutHandler.applyPlatformShortcutText(this);
|
||||||
|
|
||||||
// Setup the onClick listeners for the UI after it's been created.
|
// Setup the onClick listeners for the UI after it's been created.
|
||||||
|
@ -1419,22 +1499,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: Constants.NOTIFICATION_DISMISS_TIME
|
expiryMs: Constants.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: 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);
|
ChartEditorDialogHandler.openWelcomeDialog(this, false);
|
||||||
}
|
}
|
||||||
|
@ -1445,25 +1534,122 @@ class ChartEditorState extends HaxeUIState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override function destroy():Void
|
|
||||||
{
|
|
||||||
super.destroy();
|
|
||||||
|
|
||||||
cleanupAutoSave();
|
|
||||||
|
|
||||||
// Hide the mouse cursor on other states.
|
|
||||||
Cursor.hide();
|
|
||||||
|
|
||||||
@:privateAccess
|
|
||||||
ChartEditorNoteSprite.noteFrameCollection = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupWelcomeMusic()
|
function setupWelcomeMusic()
|
||||||
{
|
{
|
||||||
this.welcomeMusic.loadEmbedded(Paths.music('chartEditorLoop/chartEditorLoop'));
|
this.welcomeMusic.loadEmbedded(Paths.music('chartEditorLoop/chartEditorLoop'));
|
||||||
this.welcomeMusic.looped = true;
|
this.welcomeMusic.looped = true;
|
||||||
// this.welcomeMusic.play();
|
}
|
||||||
// fadeInWelcomeMusic();
|
|
||||||
|
public function loadPreferences():Void
|
||||||
|
{
|
||||||
|
var save:Save = Save.get();
|
||||||
|
|
||||||
|
if (previousWorkingFilePaths[0] == null)
|
||||||
|
{
|
||||||
|
previousWorkingFilePaths = [null].concat(save.chartEditorPreviousFiles);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
previousWorkingFilePaths = [currentWorkingFilePath].concat(save.chartEditorPreviousFiles);
|
||||||
|
}
|
||||||
|
noteSnapQuantIndex = save.chartEditorNoteQuant;
|
||||||
|
currentLiveInputStyle = save.chartEditorLiveInputStyle;
|
||||||
|
isViewDownscroll = save.chartEditorDownscroll;
|
||||||
|
playtestStartTime = save.chartEditorPlaytestStartTime;
|
||||||
|
currentTheme = save.chartEditorTheme;
|
||||||
|
isMetronomeEnabled = save.chartEditorMetronomeEnabled;
|
||||||
|
hitsoundsEnabledPlayer = save.chartEditorHitsoundsEnabledPlayer;
|
||||||
|
hitsoundsEnabledOpponent = save.chartEditorHitsoundsEnabledOpponent;
|
||||||
|
|
||||||
|
// audioInstTrack.volume = save.chartEditorInstVolume;
|
||||||
|
// audioInstTrack.pitch = save.chartEditorPlaybackSpeed;
|
||||||
|
// audioVocalTrackGroup.volume = save.chartEditorVoicesVolume;
|
||||||
|
// audioVocalTrackGroup.pitch = save.chartEditorPlaybackSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writePreferences():Void
|
||||||
|
{
|
||||||
|
var save:Save = Save.get();
|
||||||
|
|
||||||
|
// 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.chartEditorLiveInputStyle = currentLiveInputStyle;
|
||||||
|
save.chartEditorDownscroll = isViewDownscroll;
|
||||||
|
save.chartEditorPlaytestStartTime = playtestStartTime;
|
||||||
|
save.chartEditorTheme = currentTheme;
|
||||||
|
save.chartEditorMetronomeEnabled = isMetronomeEnabled;
|
||||||
|
save.chartEditorHitsoundsEnabledPlayer = hitsoundsEnabledPlayer;
|
||||||
|
save.chartEditorHitsoundsEnabledOpponent = hitsoundsEnabledOpponent;
|
||||||
|
|
||||||
|
// save.chartEditorInstVolume = audioInstTrack.volume;
|
||||||
|
// save.chartEditorVoicesVolume = audioVocalTrackGroup.volume;
|
||||||
|
// save.chartEditorPlaybackSpeed = audioInstTrack.pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function populateOpenRecentMenu():Void
|
||||||
|
{
|
||||||
|
if (menubarOpenRecent == null) return;
|
||||||
|
|
||||||
|
#if sys
|
||||||
|
menubarOpenRecent.removeAllComponents();
|
||||||
|
|
||||||
|
for (chartPath in previousWorkingFilePaths)
|
||||||
|
{
|
||||||
|
if (chartPath == null) continue;
|
||||||
|
|
||||||
|
var menuItemRecentChart:MenuItem = new MenuItem();
|
||||||
|
menuItemRecentChart.text = chartPath;
|
||||||
|
menuItemRecentChart.onClick = function(_event) {
|
||||||
|
stopWelcomeMusic();
|
||||||
|
|
||||||
|
// Load chart from file
|
||||||
|
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: Constants.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if !mac
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
|
||||||
|
menuItemRecentChart.disabled = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
menuItemRecentChart.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
menubarOpenRecent.addComponent(menuItemRecentChart);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
menubarOpenRecent.hide();
|
||||||
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
function fadeInWelcomeMusic():Void
|
function fadeInWelcomeMusic():Void
|
||||||
|
@ -1674,7 +1860,10 @@ class ChartEditorState extends HaxeUIState
|
||||||
function setNotePreviewViewportBounds(bounds:FlxRect = null):Void
|
function setNotePreviewViewportBounds(bounds:FlxRect = null):Void
|
||||||
{
|
{
|
||||||
if (notePreviewViewport == null)
|
if (notePreviewViewport == null)
|
||||||
throw 'ERROR: Tried to set note preview viewport bounds, but notePreviewViewport is null! Check ChartEditorThemeHandler.updateTheme().';
|
{
|
||||||
|
trace('[WARN] Tried to set note preview viewport bounds, but notePreviewViewport is null!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (bounds == null)
|
if (bounds == null)
|
||||||
{
|
{
|
||||||
|
@ -1780,9 +1969,14 @@ class ChartEditorState extends HaxeUIState
|
||||||
|
|
||||||
add(playbarHeadLayout);
|
add(playbarHeadLayout);
|
||||||
|
|
||||||
|
menubarOpenRecent = findComponent('menubarOpenRecent', Menu);
|
||||||
|
if (menubarOpenRecent == null) throw "Could not find menubarOpenRecent!";
|
||||||
|
|
||||||
|
menubarItemSaveChart = findComponent('menubarItemSaveChart', MenuItem);
|
||||||
|
if (menubarItemSaveChart == null) throw "Could not find menubarItemSaveChart!";
|
||||||
|
|
||||||
// Setup notifications.
|
// Setup notifications.
|
||||||
@:privateAccess
|
@:privateAccess
|
||||||
// NotificationManager.GUTTER_SIZE = 56;
|
|
||||||
NotificationManager.GUTTER_SIZE = 20;
|
NotificationManager.GUTTER_SIZE = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1811,11 +2005,21 @@ class ChartEditorState extends HaxeUIState
|
||||||
|
|
||||||
// Add functionality to the menu items.
|
// Add functionality to the menu items.
|
||||||
|
|
||||||
addUIClickListener('menubarItemNewChart', _ -> this.openWelcomeDialog(true));
|
addUIClickListener('menubarItemNewChart', _ -> ChartEditorDialogHandler.openWelcomeDialog(this, true));
|
||||||
addUIClickListener('menubarItemOpenChart', _ -> this.openBrowseFNFC(true));
|
addUIClickListener('menubarItemOpenChart', _ -> ChartEditorDialogHandler.openBrowseFNFC(this, true));
|
||||||
addUIClickListener('menubarItemSaveChartAs', _ -> this.exportAllSongData());
|
addUIClickListener('menubarItemSaveChart', _ -> {
|
||||||
addUIClickListener('menubarItemLoadInst', _ -> this.openUploadInstDialog(true));
|
if (currentWorkingFilePath != null)
|
||||||
addUIClickListener('menubarItemImportChart', _ -> this.openImportChartDialog('legacy', true));
|
{
|
||||||
|
ChartEditorImportExportHandler.exportAllSongData(this, true, currentWorkingFilePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ChartEditorImportExportHandler.exportAllSongData(this, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addUIClickListener('menubarItemSaveChartAs', _ -> ChartEditorImportExportHandler.exportAllSongData(this));
|
||||||
|
addUIClickListener('menubarItemLoadInst', _ -> ChartEditorDialogHandler.openUploadInstDialog(this, true));
|
||||||
|
addUIClickListener('menubarItemImportChart', _ -> ChartEditorDialogHandler.openImportChartDialog(this, 'legacy', true));
|
||||||
addUIClickListener('menubarItemExit', _ -> quitChartEditor());
|
addUIClickListener('menubarItemExit', _ -> quitChartEditor());
|
||||||
|
|
||||||
addUIClickListener('menubarItemUndo', _ -> undoLastCommand());
|
addUIClickListener('menubarItemUndo', _ -> undoLastCommand());
|
||||||
|
@ -2012,6 +2216,39 @@ class ChartEditorState extends HaxeUIState
|
||||||
/**
|
/**
|
||||||
* UPDATE FUNCTIONS
|
* UPDATE FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
function autoSave():Void
|
||||||
|
{
|
||||||
|
saveDataDirty = false;
|
||||||
|
|
||||||
|
// Auto-save preferences.
|
||||||
|
writePreferences();
|
||||||
|
|
||||||
|
// Auto-save the chart.
|
||||||
|
#if html5
|
||||||
|
// Auto-save to local storage.
|
||||||
|
// TODO: Implement this.
|
||||||
|
#else
|
||||||
|
// Auto-save to temp file.
|
||||||
|
ChartEditorImportExportHandler.exportAllSongData(this, true);
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWindowClose(exitCode:Int):Void
|
||||||
|
{
|
||||||
|
trace('Window exited with exit code: $exitCode');
|
||||||
|
trace('Should save chart? $saveDataDirty');
|
||||||
|
|
||||||
|
if (saveDataDirty)
|
||||||
|
{
|
||||||
|
ChartEditorImportExportHandler.exportAllSongData(this, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanupAutoSave():Void
|
||||||
|
{
|
||||||
|
WindowUtil.windowExit.remove(onWindowClose);
|
||||||
|
}
|
||||||
|
|
||||||
public override function update(elapsed:Float):Void
|
public override function update(elapsed:Float):Void
|
||||||
{
|
{
|
||||||
// Override F4 behavior to include the autosave.
|
// Override F4 behavior to include the autosave.
|
||||||
|
@ -2152,49 +2389,6 @@ class ChartEditorState extends HaxeUIState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle the playback of hitsounds.
|
|
||||||
*/
|
|
||||||
function handleHitsounds(oldSongPosition:Float, newSongPosition:Float):Void
|
|
||||||
{
|
|
||||||
if (!hitsoundsEnabled) return;
|
|
||||||
|
|
||||||
// Assume notes are sorted by time.
|
|
||||||
for (noteData in currentSongChartNoteData)
|
|
||||||
{
|
|
||||||
// Check for notes between the old and new song positions.
|
|
||||||
|
|
||||||
if (noteData.time < oldSongPosition) // Note is in the past.
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (noteData.time > newSongPosition) // Note is in the future.
|
|
||||||
return; // Assume all notes are also in the future.
|
|
||||||
|
|
||||||
// Note was just hit.
|
|
||||||
|
|
||||||
// Character preview.
|
|
||||||
|
|
||||||
// NoteScriptEvent takes a sprite, ehe. Need to rework that.
|
|
||||||
var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault());
|
|
||||||
tempNote.noteData = noteData;
|
|
||||||
tempNote.scrollFactor.set(0, 0);
|
|
||||||
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, tempNote, 1, true);
|
|
||||||
dispatchEvent(event);
|
|
||||||
|
|
||||||
// Calling event.cancelEvent() skips all the other logic! Neat!
|
|
||||||
if (event.eventCanceled) continue;
|
|
||||||
|
|
||||||
// Hitsounds.
|
|
||||||
switch (noteData.getStrumlineIndex())
|
|
||||||
{
|
|
||||||
case 0: // Player
|
|
||||||
if (hitsoundsEnabledPlayer) this.playSound(Paths.sound('chartingSounds/hitNotePlayer'));
|
|
||||||
case 1: // Opponent
|
|
||||||
if (hitsoundsEnabledOpponent) this.playSound(Paths.sound('chartingSounds/hitNoteOpponent'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle using `renderedNotes` to display notes from `currentSongChartNoteData`.
|
* Handle using `renderedNotes` to display notes from `currentSongChartNoteData`.
|
||||||
*/
|
*/
|
||||||
|
@ -3463,63 +3657,6 @@ class ChartEditorState extends HaxeUIState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles passive behavior of the menu bar, such as updating labels or enabled/disabled status.
|
|
||||||
* Does not handle onClick ACTIONS of the menubar.
|
|
||||||
*/
|
|
||||||
function handleMenubar():Void
|
|
||||||
{
|
|
||||||
if (commandHistoryDirty)
|
|
||||||
{
|
|
||||||
commandHistoryDirty = false;
|
|
||||||
|
|
||||||
// Update the Undo and Redo buttons.
|
|
||||||
var undoButton:Null<MenuItem> = findComponent('menubarItemUndo', MenuItem);
|
|
||||||
|
|
||||||
if (undoButton != null)
|
|
||||||
{
|
|
||||||
if (undoHistory.length == 0)
|
|
||||||
{
|
|
||||||
// Disable the Undo button.
|
|
||||||
undoButton.disabled = true;
|
|
||||||
undoButton.text = 'Undo';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Change the label to the last command.
|
|
||||||
undoButton.disabled = false;
|
|
||||||
undoButton.text = 'Undo ${undoHistory[undoHistory.length - 1].toString()}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
trace('undoButton is null');
|
|
||||||
}
|
|
||||||
|
|
||||||
var redoButton:Null<MenuItem> = findComponent('menubarItemRedo', MenuItem);
|
|
||||||
|
|
||||||
if (redoButton != null)
|
|
||||||
{
|
|
||||||
if (redoHistory.length == 0)
|
|
||||||
{
|
|
||||||
// Disable the Redo button.
|
|
||||||
redoButton.disabled = true;
|
|
||||||
redoButton.text = 'Redo';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Change the label to the last command.
|
|
||||||
redoButton.disabled = false;
|
|
||||||
redoButton.text = 'Redo ${redoHistory[redoHistory.length - 1].toString()}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
trace('redoButton is null');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleToolboxes():Void
|
function handleToolboxes():Void
|
||||||
{
|
{
|
||||||
handleDifficultyToolbox();
|
handleDifficultyToolbox();
|
||||||
|
@ -3743,32 +3880,6 @@ class ChartEditorState extends HaxeUIState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the note preview/scroll area on the right side.
|
|
||||||
* Notes are rendered here as small bars.
|
|
||||||
* This function also handles:
|
|
||||||
* - Moving the viewport preview box around based on its current position.
|
|
||||||
* - Scrolling the note preview area down if the note preview is taller than the screen,
|
|
||||||
* and the viewport nears the end of the visible area.
|
|
||||||
*/
|
|
||||||
function handleNotePreview():Void
|
|
||||||
{
|
|
||||||
if (notePreviewDirty && notePreview != null)
|
|
||||||
{
|
|
||||||
notePreviewDirty = false;
|
|
||||||
|
|
||||||
// TODO: Only update the notes that have changed.
|
|
||||||
notePreview.erase();
|
|
||||||
notePreview.addNotes(currentSongChartNoteData, Std.int(songLengthInMs));
|
|
||||||
notePreview.addEvents(currentSongChartEventData, Std.int(songLengthInMs));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notePreviewViewportBoundsDirty)
|
|
||||||
{
|
|
||||||
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle aligning the health icons next to the grid.
|
* Handle aligning the health icons next to the grid.
|
||||||
*/
|
*/
|
||||||
|
@ -3825,12 +3936,24 @@ class ChartEditorState extends HaxeUIState
|
||||||
this.openBrowseFNFC(true);
|
this.openBrowseFNFC(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CTRL + SHIFT + S = Save As
|
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.S)
|
||||||
|
{
|
||||||
|
if (currentWorkingFilePath == null || FlxG.keys.pressed.SHIFT)
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
{
|
{
|
||||||
this.exportAllSongData(false);
|
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)
|
||||||
{
|
{
|
||||||
|
@ -3846,6 +3969,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4119,48 +4244,6 @@ class ChartEditorState extends HaxeUIState
|
||||||
performCommand(command, false);
|
performCommand(command, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* SAVE, AUTOSAVE, QUIT FUNCTIONS
|
|
||||||
*/
|
|
||||||
// ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after 5 minutes without saving.
|
|
||||||
*/
|
|
||||||
function autoSave():Void
|
|
||||||
{
|
|
||||||
saveDataDirty = false;
|
|
||||||
|
|
||||||
// Auto-save the chart.
|
|
||||||
|
|
||||||
#if html5
|
|
||||||
// Auto-save to local storage.
|
|
||||||
#else
|
|
||||||
// Auto-save to temp file.
|
|
||||||
this.exportAllSongData(true);
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the window is closed while we are in the chart editor.
|
|
||||||
* @param exitCode The exit code of the window.
|
|
||||||
*/
|
|
||||||
function onWindowClose(exitCode:Int):Void
|
|
||||||
{
|
|
||||||
trace('Window exited with exit code: $exitCode');
|
|
||||||
trace('Should save chart? $saveDataDirty');
|
|
||||||
|
|
||||||
if (saveDataDirty)
|
|
||||||
{
|
|
||||||
this.exportAllSongData(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanupAutoSave():Void
|
|
||||||
{
|
|
||||||
WindowUtil.windowExit.remove(onWindowClose);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GRAPHICS FUNCTIONS
|
* GRAPHICS FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
@ -4208,28 +4291,6 @@ class ChartEditorState extends HaxeUIState
|
||||||
setComponentText('playbarPlay', '||');
|
setComponentText('playbarPlay', '||');
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopAudioPlayback():Void
|
|
||||||
{
|
|
||||||
if (audioInstTrack != null) audioInstTrack.pause();
|
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
|
||||||
|
|
||||||
setComponentText('playbarPlay', '>');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleAudioPlayback():Void
|
|
||||||
{
|
|
||||||
if (audioInstTrack == null) return;
|
|
||||||
|
|
||||||
if (audioInstTrack.playing)
|
|
||||||
{
|
|
||||||
stopAudioPlayback();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
startAudioPlayback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play the metronome tick sound.
|
* Play the metronome tick sound.
|
||||||
* @param high Whether to play the full beat sound rather than the quarter beat sound.
|
* @param high Whether to play the full beat sound rather than the quarter beat sound.
|
||||||
|
@ -4245,42 +4306,6 @@ class ChartEditorState extends HaxeUIState
|
||||||
this.switchToInstrumental(currentInstrumentalId, currentSongMetadata.playData.characters.player, currentSongMetadata.playData.characters.opponent);
|
this.switchToInstrumental(currentInstrumentalId, currentSongMetadata.playData.characters.player, currentSongMetadata.playData.characters.opponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function postLoadInstrumental():Void
|
|
||||||
{
|
|
||||||
if (audioInstTrack != null)
|
|
||||||
{
|
|
||||||
// Prevent the time from skipping back to 0 when the song ends.
|
|
||||||
audioInstTrack.onComplete = function() {
|
|
||||||
if (audioInstTrack != null) audioInstTrack.pause();
|
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
|
||||||
};
|
|
||||||
|
|
||||||
songLengthInMs = audioInstTrack.length;
|
|
||||||
|
|
||||||
if (gridTiledSprite != null) gridTiledSprite.height = songLengthInPixels;
|
|
||||||
if (gridPlayheadScrollArea != null)
|
|
||||||
{
|
|
||||||
gridPlayheadScrollArea.setGraphicSize(Std.int(gridPlayheadScrollArea.width), songLengthInPixels);
|
|
||||||
gridPlayheadScrollArea.updateHitbox();
|
|
||||||
}
|
|
||||||
|
|
||||||
buildSpectrogram(audioInstTrack);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
trace('[WARN] Instrumental track was null!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pretty much everything is going to need to be reset.
|
|
||||||
scrollPositionInPixels = 0;
|
|
||||||
playheadPositionInPixels = 0;
|
|
||||||
notePreviewDirty = true;
|
|
||||||
notePreviewViewportBoundsDirty = true;
|
|
||||||
noteDisplayDirty = true;
|
|
||||||
healthIconsDirty = true;
|
|
||||||
moveSongToScrollPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CHART DATA FUNCTIONS
|
* CHART DATA FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
@ -4299,11 +4324,6 @@ class ChartEditorState extends HaxeUIState
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNoteSelected(note:Null<SongNoteData>):Bool
|
|
||||||
{
|
|
||||||
return note != null && currentNoteSelection.indexOf(note) != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEventSelected(event:Null<SongEventData>):Bool
|
function isEventSelected(event:Null<SongEventData>):Bool
|
||||||
{
|
{
|
||||||
return event != null && currentEventSelection.indexOf(event) != -1;
|
return event != null && currentEventSelection.indexOf(event) != -1;
|
||||||
|
@ -4679,11 +4699,250 @@ class ChartEditorState extends HaxeUIState
|
||||||
/**
|
/**
|
||||||
* Dismiss any existing HaxeUI notifications, if there are any.
|
* Dismiss any existing HaxeUI notifications, if there are any.
|
||||||
*/
|
*/
|
||||||
public static function dismissNotifications():Void
|
function handleNotePreview():Void
|
||||||
|
{
|
||||||
|
if (notePreviewDirty && notePreview != null)
|
||||||
|
{
|
||||||
|
notePreviewDirty = false;
|
||||||
|
|
||||||
|
// TODO: Only update the notes that have changed.
|
||||||
|
notePreview.erase();
|
||||||
|
notePreview.addNotes(currentSongChartNoteData, Std.int(songLengthInMs));
|
||||||
|
notePreview.addEvents(currentSongChartEventData, Std.int(songLengthInMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notePreviewViewportBoundsDirty)
|
||||||
|
{
|
||||||
|
setNotePreviewViewportBounds(calculateNotePreviewViewportBounds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles passive behavior of the menu bar, such as updating labels or enabled/disabled status.
|
||||||
|
* Does not handle onClick ACTIONS of the menubar.
|
||||||
|
*/
|
||||||
|
function handleMenubar():Void
|
||||||
|
{
|
||||||
|
if (commandHistoryDirty)
|
||||||
|
{
|
||||||
|
commandHistoryDirty = false;
|
||||||
|
|
||||||
|
// Update the Undo and Redo buttons.
|
||||||
|
var undoButton:Null<MenuItem> = findComponent('menubarItemUndo', MenuItem);
|
||||||
|
|
||||||
|
if (undoButton != null)
|
||||||
|
{
|
||||||
|
if (undoHistory.length == 0)
|
||||||
|
{
|
||||||
|
// Disable the Undo button.
|
||||||
|
undoButton.disabled = true;
|
||||||
|
undoButton.text = 'Undo';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Change the label to the last command.
|
||||||
|
undoButton.disabled = false;
|
||||||
|
undoButton.text = 'Undo ${undoHistory[undoHistory.length - 1].toString()}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('undoButton is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
var redoButton:Null<MenuItem> = findComponent('menubarItemRedo', MenuItem);
|
||||||
|
|
||||||
|
if (redoButton != null)
|
||||||
|
{
|
||||||
|
if (redoHistory.length == 0)
|
||||||
|
{
|
||||||
|
// Disable the Redo button.
|
||||||
|
redoButton.disabled = true;
|
||||||
|
redoButton.text = 'Redo';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Change the label to the last command.
|
||||||
|
redoButton.disabled = false;
|
||||||
|
redoButton.text = 'Redo ${redoHistory[redoHistory.length - 1].toString()}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('redoButton is null');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the playback of hitsounds.
|
||||||
|
*/
|
||||||
|
function handleHitsounds(oldSongPosition:Float, newSongPosition:Float):Void
|
||||||
|
{
|
||||||
|
if (!hitsoundsEnabled) return;
|
||||||
|
|
||||||
|
// Assume notes are sorted by time.
|
||||||
|
for (noteData in currentSongChartNoteData)
|
||||||
|
{
|
||||||
|
// Check for notes between the old and new song positions.
|
||||||
|
|
||||||
|
if (noteData.time < oldSongPosition) // Note is in the past.
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (noteData.time > newSongPosition) // Note is in the future.
|
||||||
|
return; // Assume all notes are also in the future.
|
||||||
|
|
||||||
|
// Note was just hit.
|
||||||
|
|
||||||
|
// Character preview.
|
||||||
|
|
||||||
|
// NoteScriptEvent takes a sprite, ehe. Need to rework that.
|
||||||
|
var tempNote:NoteSprite = new NoteSprite(NoteStyleRegistry.instance.fetchDefault());
|
||||||
|
tempNote.noteData = noteData;
|
||||||
|
tempNote.scrollFactor.set(0, 0);
|
||||||
|
var event:NoteScriptEvent = new NoteScriptEvent(NOTE_HIT, tempNote, 1, true);
|
||||||
|
dispatchEvent(event);
|
||||||
|
|
||||||
|
// Calling event.cancelEvent() skips all the other logic! Neat!
|
||||||
|
if (event.eventCanceled) continue;
|
||||||
|
|
||||||
|
// Hitsounds.
|
||||||
|
switch (noteData.getStrumlineIndex())
|
||||||
|
{
|
||||||
|
case 0: // Player
|
||||||
|
if (hitsoundsEnabledPlayer) ChartEditorAudioHandler.playSound(this, Paths.sound('chartingSounds/hitNotePlayer'));
|
||||||
|
case 1: // Opponent
|
||||||
|
if (hitsoundsEnabledOpponent) ChartEditorAudioHandler.playSound(this, Paths.sound('chartingSounds/hitNoteOpponent'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAudioPlayback():Void
|
||||||
|
{
|
||||||
|
if (audioInstTrack != null) audioInstTrack.pause();
|
||||||
|
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
||||||
|
|
||||||
|
setComponentText('playbarPlay', '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAudioPlayback():Void
|
||||||
|
{
|
||||||
|
if (audioInstTrack == null) return;
|
||||||
|
|
||||||
|
if (audioInstTrack.playing)
|
||||||
|
{
|
||||||
|
stopAudioPlayback();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
startAudioPlayback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postLoadInstrumental():Void
|
||||||
|
{
|
||||||
|
if (audioInstTrack != null)
|
||||||
|
{
|
||||||
|
// Prevent the time from skipping back to 0 when the song ends.
|
||||||
|
audioInstTrack.onComplete = function() {
|
||||||
|
if (audioInstTrack != null) audioInstTrack.pause();
|
||||||
|
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
||||||
|
};
|
||||||
|
|
||||||
|
songLengthInMs = audioInstTrack.length;
|
||||||
|
|
||||||
|
if (gridTiledSprite != null) gridTiledSprite.height = songLengthInPixels;
|
||||||
|
if (gridPlayheadScrollArea != null)
|
||||||
|
{
|
||||||
|
gridPlayheadScrollArea.setGraphicSize(Std.int(gridPlayheadScrollArea.width), songLengthInPixels);
|
||||||
|
gridPlayheadScrollArea.updateHitbox();
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSpectrogram(audioInstTrack);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[WARN] Instrumental track was null!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty much everything is going to need to be reset.
|
||||||
|
scrollPositionInPixels = 0;
|
||||||
|
playheadPositionInPixels = 0;
|
||||||
|
notePreviewDirty = true;
|
||||||
|
notePreviewViewportBoundsDirty = true;
|
||||||
|
noteDisplayDirty = true;
|
||||||
|
healthIconsDirty = true;
|
||||||
|
moveSongToScrollPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the voices group.
|
||||||
|
*/
|
||||||
|
public function clearVocals():Void
|
||||||
|
{
|
||||||
|
if (audioVocalTrackGroup != null) audioVocalTrackGroup.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNoteSelected(note:Null<SongNoteData>):Bool
|
||||||
|
{
|
||||||
|
return note != null && currentNoteSelection.indexOf(note) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
override function destroy():Void
|
||||||
|
{
|
||||||
|
super.destroy();
|
||||||
|
|
||||||
|
cleanupAutoSave();
|
||||||
|
|
||||||
|
// Hide the mouse cursor on other states.
|
||||||
|
Cursor.hide();
|
||||||
|
|
||||||
|
@:privateAccess
|
||||||
|
ChartEditorNoteSprite.noteFrameCollection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dismiss any existing notifications, if there are any.
|
||||||
|
*/
|
||||||
|
function dismissNotifications():Void
|
||||||
{
|
{
|
||||||
NotificationManager.instance.clearNotifications();
|
NotificationManager.instance.clearNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyCanQuickSave():Void
|
||||||
|
{
|
||||||
|
if (menubarItemSaveChart == null) return;
|
||||||
|
|
||||||
|
if (currentWorkingFilePath == null)
|
||||||
|
{
|
||||||
|
menubarItemSaveChart.disabled = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
menubarItemSaveChart.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyWindowTitle():Void
|
||||||
|
{
|
||||||
|
var inner:String = 'New Chart';
|
||||||
|
var cwfp:Null<String> = currentWorkingFilePath;
|
||||||
|
if (cwfp != null)
|
||||||
|
{
|
||||||
|
inner = cwfp;
|
||||||
|
}
|
||||||
|
if (currentWorkingFilePath == null || saveDataDirty)
|
||||||
|
{
|
||||||
|
inner += '*';
|
||||||
|
}
|
||||||
|
WindowUtil.setWindowTitle('Friday Night Funkin\' Chart Editor - ${inner}');
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetWindowTitle():Void
|
||||||
|
{
|
||||||
|
WindowUtil.setWindowTitle('Friday Night Funkin\'');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a note data value into a chart editor grid column number.
|
* Convert a note data value into a chart editor grid column number.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -217,6 +217,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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a list of ZIP file entries from the current loaded instrumental tracks in the chart eidtor.
|
* Create a list of ZIP file entries from the current loaded instrumental tracks in the chart eidtor.
|
||||||
* @param state The chart editor state.
|
* @param state The chart editor state.
|
||||||
|
@ -226,18 +238,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,10 +275,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,11 +85,73 @@ class ChartEditorDialogHandler
|
||||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable);
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable);
|
||||||
if (dialog == null) throw 'Could not locate Welcome dialog';
|
if (dialog == null) throw 'Could not locate Welcome dialog';
|
||||||
|
|
||||||
|
state.isHaxeUIDialogOpen = true;
|
||||||
dialog.onDialogClosed = function(_event) {
|
dialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
// Called when the Welcome dialog is closed while it is closable.
|
// Called when the Welcome dialog is closed while it is closable.
|
||||||
state.stopWelcomeMusic();
|
state.stopWelcomeMusic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if sys
|
||||||
|
var splashRecentContainer:Null<VBox> = dialog.findComponent('splashRecentContainer', VBox);
|
||||||
|
if (splashRecentContainer == null) throw 'Could not locate splashRecentContainer in Welcome dialog';
|
||||||
|
|
||||||
|
for (chartPath in state.previousWorkingFilePaths)
|
||||||
|
{
|
||||||
|
if (chartPath == null) continue;
|
||||||
|
|
||||||
|
var linkRecentChart:Link = new Link();
|
||||||
|
linkRecentChart.text = chartPath;
|
||||||
|
linkRecentChart.onClick = function(_event) {
|
||||||
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
|
state.stopWelcomeMusic();
|
||||||
|
|
||||||
|
// Load chart from file
|
||||||
|
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: Constants.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#if !mac
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
|
||||||
|
linkRecentChart.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
splashRecentContainer.addComponent(linkRecentChart);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
var splashRecentContainer:Null<VBox> = dialog.findComponent('splashRecentContainer', VBox);
|
||||||
|
if (splashRecentContainer == null) throw 'Could not locate splashRecentContainer in Welcome dialog';
|
||||||
|
|
||||||
|
var webLoadLabel:Label = new Label();
|
||||||
|
webLoadLabel.text = 'Click the button below to load a chart file (.fnfc) from your computer.';
|
||||||
|
|
||||||
|
splashRecentContainer.add(webLoadLabel);
|
||||||
|
#end
|
||||||
|
|
||||||
// Create New Song "Easy/Normal/Hard"
|
// Create New Song "Easy/Normal/Hard"
|
||||||
var linkCreateBasic:Null<Link> = dialog.findComponent('splashCreateFromSongBasic', Link);
|
var linkCreateBasic:Null<Link> = dialog.findComponent('splashCreateFromSongBasic', Link);
|
||||||
if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasic link in Welcome dialog';
|
if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasic link in Welcome dialog';
|
||||||
|
@ -181,6 +243,7 @@ class ChartEditorDialogHandler
|
||||||
if (dialog == null) throw 'Could not locate Upload Chart dialog';
|
if (dialog == null) throw 'Could not locate Upload Chart dialog';
|
||||||
|
|
||||||
dialog.onDialogClosed = function(_event) {
|
dialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (_event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Simply let the dialog close.
|
// Simply let the dialog close.
|
||||||
|
@ -195,6 +258,7 @@ class ChartEditorDialogHandler
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Chart dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Chart dialog';
|
||||||
|
|
||||||
|
state.isHaxeUIDialogOpen = true;
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_event) {
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
@ -221,7 +285,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(
|
||||||
|
@ -260,21 +325,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: Constants.NOTIFICATION_DISMISS_TIME
|
expiryMs: Constants.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: Constants.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
#end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (err)
|
catch (err)
|
||||||
{
|
{
|
||||||
|
@ -320,6 +397,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,6 +438,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -396,6 +477,7 @@ 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.switchToCurrentInstrumental();
|
||||||
state.postLoadInstrumental();
|
state.postLoadInstrumental();
|
||||||
}
|
}
|
||||||
|
@ -454,6 +536,7 @@ class ChartEditorDialogHandler
|
||||||
var uploadVocalsDialogErect:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
var uploadVocalsDialogErect:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
uploadVocalsDialogErect.onDialogClosed = function(_event) {
|
uploadVocalsDialogErect.onDialogClosed = function(_event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
state.currentWorkingFilePath = null; // New file, so no path.
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
state.postLoadInstrumental();
|
state.postLoadInstrumental();
|
||||||
}
|
}
|
||||||
|
@ -630,7 +713,9 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Song Metadata dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Song Metadata dialog';
|
||||||
|
state.isHaxeUIDialogOpen = true;
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -745,6 +830,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);
|
||||||
|
@ -1352,7 +1438,9 @@ class ChartEditorDialogHandler
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Import Chart dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Import Chart dialog';
|
||||||
|
|
||||||
|
state.isHaxeUIDialogOpen = true;
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
dialog.hideDialog(DialogButton.CANCEL);
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1513,7 +1601,9 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
// If all validators succeeded, this callback is called.
|
// If all validators succeeded, this callback is called.
|
||||||
|
|
||||||
|
state.isHaxeUIDialogOpen = true;
|
||||||
variationForm.onSubmit = function(_event) {
|
variationForm.onSubmit = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
trace('Add Variation dialog submitted, validation succeeded!');
|
trace('Add Variation dialog submitted, validation succeeded!');
|
||||||
|
|
||||||
var dialogVariationName:Null<TextField> = dialog.findComponent('dialogVariationName', TextField);
|
var dialogVariationName:Null<TextField> = dialog.findComponent('dialogVariationName', TextField);
|
||||||
|
|
|
@ -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,6 +53,8 @@ class ChartEditorImportExportHandler
|
||||||
|
|
||||||
state.sortChartData();
|
state.sortChartData();
|
||||||
|
|
||||||
|
ChartEditorAudioHandler.wipeInstrumentalData(state);
|
||||||
|
ChartEditorAudioHandler.wipeVocalData(state);
|
||||||
state.stopExistingVocals();
|
state.stopExistingVocals();
|
||||||
|
|
||||||
var variations:Array<String> = state.availableVariations;
|
var variations:Array<String> = state.availableVariations;
|
||||||
|
@ -91,7 +93,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 +143,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 +245,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 +278,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 +296,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 ($opponentCharId).';
|
warnings.push('Could not find vocals ($opponentVocalsFileName).');
|
||||||
|
// throw 'Could not find vocals ($opponentVocalsFileName).';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,7 +315,7 @@ class ChartEditorImportExportHandler
|
||||||
|
|
||||||
state.switchToCurrentInstrumental();
|
state.switchToCurrentInstrumental();
|
||||||
|
|
||||||
return true;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -345,8 +363,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,13 +375,24 @@ 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
|
||||||
{
|
{
|
||||||
// Prompt and save.
|
// Prompt and save.
|
||||||
var onSave:Array<String>->Void = function(paths:Array<String>) {
|
var onSave:Array<String>->Void = function(paths:Array<String>) {
|
||||||
trace('Successfully exported files.');
|
if (paths.length != 1)
|
||||||
|
{
|
||||||
|
trace('[WARN] Could not get save path.');
|
||||||
|
state.applyWindowTitle();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('Saved to "${paths[0]}"');
|
||||||
|
state.currentWorkingFilePath = paths[0];
|
||||||
|
state.applyWindowTitle();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onCancel:Void->Void = function() {
|
var onCancel:Void->Void = function() {
|
||||||
|
@ -372,6 +403,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) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -402,8 +402,12 @@ class Constants
|
||||||
public static final GHOST_TAPPING:Bool = false;
|
public static final GHOST_TAPPING:Bool = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The separator between an asset library and the asset path.
|
* The maximum number of previous file paths for the Chart Editor to remember.
|
||||||
|
*/
|
||||||
|
public static final MAX_PREVIOUS_WORKING_FILES:Int = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The separator between an asset library and the asset path.
|
||||||
*/
|
*/
|
||||||
public static final LIBRARY_SEPARATOR:String = ':';
|
public static final LIBRARY_SEPARATOR:String = ':';
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -344,13 +346,22 @@ class FileUtil
|
||||||
public static function readBytesFromPath(path:String):Bytes
|
public static function readBytesFromPath(path:String):Bytes
|
||||||
{
|
{
|
||||||
#if sys
|
#if sys
|
||||||
if (!sys.FileSystem.exists(path)) return null;
|
if (!doesFileExist(path)) return null;
|
||||||
return sys.io.File.getBytes(path);
|
return sys.io.File.getBytes(path);
|
||||||
#else
|
#else
|
||||||
return null;
|
return null;
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function doesFileExist(path:String):Bool
|
||||||
|
{
|
||||||
|
#if sys
|
||||||
|
return sys.FileSystem.exists(path);
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#end
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browse for a file to read and execute a callback once we have a file reference.
|
* Browse for a file to read and execute a callback once we have a file reference.
|
||||||
* Works great on HTML5 or desktop.
|
* Works great on HTML5 or desktop.
|
||||||
|
@ -434,18 +445,20 @@ class FileUtil
|
||||||
case Force:
|
case Force:
|
||||||
sys.io.File.saveContent(path, data);
|
sys.io.File.saveContent(path, data);
|
||||||
case Skip:
|
case Skip:
|
||||||
if (!sys.FileSystem.exists(path))
|
if (!doesFileExist(path))
|
||||||
{
|
{
|
||||||
sys.io.File.saveContent(path, data);
|
sys.io.File.saveContent(path, data);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw 'File already exists: $path';
|
// Do nothing.
|
||||||
|
// throw 'File already exists: $path';
|
||||||
}
|
}
|
||||||
case Ask:
|
case Ask:
|
||||||
if (sys.FileSystem.exists(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
|
||||||
{
|
{
|
||||||
|
@ -475,18 +488,20 @@ class FileUtil
|
||||||
case Force:
|
case Force:
|
||||||
sys.io.File.saveBytes(path, data);
|
sys.io.File.saveBytes(path, data);
|
||||||
case Skip:
|
case Skip:
|
||||||
if (!sys.FileSystem.exists(path))
|
if (!doesFileExist(path))
|
||||||
{
|
{
|
||||||
sys.io.File.saveBytes(path, data);
|
sys.io.File.saveBytes(path, data);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw 'File already exists: $path';
|
// Do nothing.
|
||||||
|
// throw 'File already exists: $path';
|
||||||
}
|
}
|
||||||
case Ask:
|
case Ask:
|
||||||
if (sys.FileSystem.exists(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
|
||||||
{
|
{
|
||||||
|
@ -523,7 +538,7 @@ class FileUtil
|
||||||
public static function createDirIfNotExists(dir:String):Void
|
public static function createDirIfNotExists(dir:String):Void
|
||||||
{
|
{
|
||||||
#if sys
|
#if sys
|
||||||
if (!sys.FileSystem.exists(dir))
|
if (!doesFileExist(dir))
|
||||||
{
|
{
|
||||||
sys.FileSystem.createDirectory(dir);
|
sys.FileSystem.createDirectory(dir);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue