diff --git a/assets b/assets index 118b62295..c1cea2051 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 118b622953171aaf127cb160538e21bc468620e2 +Subproject commit c1cea20513dfa93e3e74a0db98498b2fd8da50fc diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 54b66605c..dcf7f9f0d 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -1,16 +1,19 @@ package funkin.save; import flixel.util.FlxSave; -import funkin.save.migrator.SaveDataMigrator; -import thx.semver.Version; import funkin.Controls.Device; import funkin.save.migrator.RawSaveData_v1_0_0; +import funkin.save.migrator.SaveDataMigrator; +import funkin.ui.debug.charting.ChartEditorState.LiveInputStyle; +import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme; +import thx.semver.Version; @:nullSafety @:forward(volume, mute) 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"; // We load this version's saves from a new save path, to maintain SOME level of backwards compatibility. @@ -94,6 +97,18 @@ abstract Save(RawSaveData) optionsChartEditor: { // Reasonable defaults. + previousFiles: [], + noteQuant: 3, + liveInputStyle: LiveInputStyle.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 +139,9 @@ abstract Save(RawSaveData) 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>; @@ -136,7 +153,213 @@ abstract Save(RawSaveData) 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):LiveInputStyle; + + function get_chartEditorLiveInputStyle():LiveInputStyle + { + if (this.optionsChartEditor.liveInputStyle == null) this.optionsChartEditor.liveInputStyle = LiveInputStyle.None; + + return this.optionsChartEditor.liveInputStyle; + } + + function set_chartEditorLiveInputStyle(value:LiveInputStyle):LiveInputStyle + { + // Set and apply. + this.optionsChartEditor.liveInputStyle = value; + flush(); + return this.optionsChartEditor.liveInputStyle; + } + + 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 +922,77 @@ typedef SaveControlsData = /** * 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 `LiveInputStyle.None` + */ + var ?liveInputStyle:LiveInputStyle; + + /** + * 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; +}; diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index dd5ddb06c..dd874577a 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -84,11 +84,47 @@ class ChartEditorDialogHandler var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable); if (dialog == null) throw 'Could not locate Welcome dialog'; + state.isHaxeUIDialogOpen = true; dialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; // Called when the Welcome dialog is closed while it is closable. 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) + { + var linkRecentChart:Link = new FunkinLink(); + linkRecentChart.text = chartPath; + linkRecentChart.onClick = function(_event) { + dialog.hideDialog(DialogButton.CANCEL); + state.stopWelcomeMusic(); + + // Load chart from file + ChartEditorImportExportHandler.loadFromFNFCPath(state, chartPath); + } + + 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" var linkCreateBasic:Null<Link> = dialog.findComponent('splashCreateFromSongBasic', Link); if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasic link in Welcome dialog'; @@ -180,6 +216,7 @@ class ChartEditorDialogHandler if (dialog == null) throw 'Could not locate Upload Chart dialog'; dialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; if (_event.button == DialogButton.APPLY) { // Simply let the dialog close. @@ -194,6 +231,7 @@ class ChartEditorDialogHandler var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button); if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Chart dialog'; + state.isHaxeUIDialogOpen = true; buttonCancel.onClick = function(_event) { dialog.hideDialog(DialogButton.CANCEL); } @@ -232,6 +270,10 @@ class ChartEditorDialogHandler }); #end + trace(selectedFile.name); + trace(selectedFile.text); + trace(selectedFile.isBinary); + trace(selectedFile.fullPath); if (selectedFile.fullPath != null) state.currentWorkingFilePath = selectedFile.fullPath; dialog.hideDialog(DialogButton.APPLY); removeDropHandler(onDropFile); @@ -689,7 +731,9 @@ class ChartEditorDialogHandler var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button); if (buttonCancel == null) throw 'Could not locate dialogCancel button in Song Metadata dialog'; + state.isHaxeUIDialogOpen = true; buttonCancel.onClick = function(_event) { + state.isHaxeUIDialogOpen = false; dialog.hideDialog(DialogButton.CANCEL); } @@ -1411,7 +1455,9 @@ class ChartEditorDialogHandler var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button); if (buttonCancel == null) throw 'Could not locate dialogCancel button in Import Chart dialog'; + state.isHaxeUIDialogOpen = true; buttonCancel.onClick = function(_event) { + state.isHaxeUIDialogOpen = false; dialog.hideDialog(DialogButton.CANCEL); } @@ -1596,7 +1642,9 @@ class ChartEditorDialogHandler // If all validators succeeded, this callback is called. + state.isHaxeUIDialogOpen = true; variationForm.onSubmit = function(_event) { + state.isHaxeUIDialogOpen = false; trace('Add Variation dialog submitted, validation succeeded!'); var dialogVariationName:Null<TextField> = dialog.findComponent('dialogVariationName', TextField); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index d392c2c06..831afe738 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1,25 +1,18 @@ package funkin.ui.debug.charting; -import funkin.play.stage.StageData; -import funkin.play.character.CharacterData.CharacterDataParser; -import funkin.play.character.CharacterData; -import flixel.system.FlxAssets.FlxSoundAsset; -import flixel.math.FlxMath; -import haxe.ui.components.TextField; -import haxe.ui.components.DropDown; -import haxe.ui.components.NumberStepper; -import haxe.ui.containers.Frame; import flixel.addons.display.FlxSliceSprite; import flixel.addons.display.FlxTiledSprite; +import flixel.addons.transition.FlxTransitionableState; import flixel.FlxCamera; import flixel.FlxSprite; import flixel.FlxSubState; import flixel.group.FlxSpriteGroup; -import flixel.addons.transition.FlxTransitionableState; import flixel.input.keyboard.FlxKey; +import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.math.FlxRect; import flixel.sound.FlxSound; +import flixel.system.FlxAssets.FlxSoundAsset; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.tweens.misc.VarTween; @@ -29,28 +22,31 @@ import flixel.util.FlxTimer; import funkin.audio.visualize.PolygonSpectogram; import funkin.audio.VoicesGroup; import funkin.data.notestyle.NoteStyleRegistry; -import funkin.data.notestyle.NoteStyleRegistry; +import funkin.data.song.SongData.SongCharacterData; +import funkin.data.song.SongData.SongChartData; +import funkin.data.song.SongData.SongEventData; +import funkin.data.song.SongData.SongMetadata; +import funkin.data.song.SongData.SongNoteData; +import funkin.data.song.SongDataUtils; +import funkin.data.song.SongRegistry; import funkin.input.Cursor; import funkin.input.TurboKeyHandler; import funkin.modding.events.ScriptEvent; import funkin.play.character.BaseCharacter.CharacterType; +import funkin.play.character.CharacterData; +import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.HealthIcon; import funkin.play.notes.NoteSprite; import funkin.play.notes.Strumline; import funkin.play.PlayState; import funkin.play.song.Song; -import funkin.data.song.SongData.SongChartData; -import funkin.data.song.SongRegistry; -import funkin.data.song.SongData.SongEventData; -import funkin.data.song.SongData.SongMetadata; -import funkin.data.song.SongData.SongNoteData; -import funkin.data.song.SongData.SongCharacterData; -import funkin.data.song.SongDataUtils; -import funkin.ui.debug.charting.ChartEditorCommand; +import funkin.play.stage.StageData; +import funkin.save.Save; import funkin.ui.debug.charting.ChartEditorCommand; import funkin.ui.debug.charting.ChartEditorThemeHandler.ChartEditorTheme; import funkin.ui.debug.charting.ChartEditorToolboxHandler.ChartEditorToolMode; import funkin.ui.haxeui.components.CharacterPlayer; +import funkin.ui.haxeui.components.FunkinMenuItem; import funkin.ui.haxeui.HaxeUIState; import funkin.util.Constants; import funkin.util.DateUtil; @@ -61,10 +57,14 @@ import funkin.util.WindowUtil; import haxe.DynamicAccess; import haxe.io.Bytes; import haxe.io.Path; +import haxe.ui.components.DropDown; import haxe.ui.components.Label; +import haxe.ui.components.NumberStepper; import haxe.ui.components.Slider; +import haxe.ui.components.TextField; import haxe.ui.containers.dialogs.CollapsibleDialog; -import haxe.ui.containers.menus.MenuItem; +import haxe.ui.containers.Frame; +import haxe.ui.containers.menus.Menu; import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeViewNode; import haxe.ui.core.Component; @@ -594,27 +594,6 @@ class ChartEditorState extends HaxeUIState return selectedCharacter; } - /** - * Whether the user is currently in Pattern Mode. - * This overrides the chart editor's normal behavior. - */ - var isInPatternMode(default, set):Bool = false; - - function set_isInPatternMode(value:Bool):Bool - { - isInPatternMode = value; - - // Make sure view is updated when we change modes. - noteDisplayDirty = true; - notePreviewDirty = true; - notePreviewViewportBoundsDirty = true; - this.scrollPositionInPixels = 0; - - return isInPatternMode; - } - - var currentPattern:String = ''; - /** * Whether the note display render group has been modified and needs to be updated. * This happens when we scroll or add/remove notes, and need to update what notes are displayed and where. @@ -1183,6 +1162,11 @@ class ChartEditorState extends HaxeUIState */ var playbarHeadLayout:Null<Component> = null; + /** + * The submenu in the menubar containing recently opened files. + */ + var menubarOpenRecent:Null<Menu> = null; + /** * The playbar head slider. */ @@ -1237,10 +1221,50 @@ class ChartEditorState extends HaxeUIState */ var params:Null<ChartEditorParams>; + /** + * A list of previous working file paths. + * Also known as the "recent files" list. + */ + public var previousWorkingFilePaths:Array<String> = []; + /** * The current file path which the chart editor is working with. */ - public var currentWorkingFilePath:Null<String>; + 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 == null) return null; + + if (value == previousWorkingFilePaths[0]) return value; + + 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 oldest path. + previousWorkingFilePaths.pop(); + } + + populateOpenRecentMenu(); + + return value; + } public function new(?params:ChartEditorParams) { @@ -1260,6 +1284,8 @@ class ChartEditorState extends HaxeUIState // Show the mouse cursor. Cursor.show(); + loadPreferences(); + fixCamera(); // Get rid of any music from the previous state. @@ -1280,6 +1306,7 @@ class ChartEditorState extends HaxeUIState buildSelectionBox(); buildAdditionalUI(); + populateOpenRecentMenu(); // Setup the onClick listeners for the UI after it's been created. setupUIListeners(); @@ -1322,8 +1349,80 @@ class ChartEditorState extends HaxeUIState { this.welcomeMusic.loadEmbedded(Paths.music('chartEditorLoop/chartEditorLoop')); this.welcomeMusic.looped = true; - // this.welcomeMusic.play(); - // fadeInWelcomeMusic(); + } + + public function loadPreferences():Void + { + var save:Save = Save.get(); + + previousWorkingFilePaths = 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(); + + save.chartEditorPreviousFiles = previousWorkingFilePaths; + 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.clear(); + + for (chartPath in previousWorkingFilePaths) + { + var menuItemRecentChart:FunkinMenuItem = new FunkinMenuItem(); + menuItemRecentChart.text = chartPath; + menuItemRecentChart.onClick = function(_event) { + stopWelcomeMusic(); + + // Load chart from file + ChartEditorImportExportHandler.loadFromFNFCPath(this, chartPath); + } + + 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 } public function fadeInWelcomeMusic():Void @@ -1540,7 +1639,10 @@ class ChartEditorState extends HaxeUIState function setNotePreviewViewportBounds(bounds:FlxRect = null):Void { 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) { @@ -1646,9 +1748,11 @@ class ChartEditorState extends HaxeUIState add(playbarHeadLayout); + menubarOpenRecent = findComponent('menubarOpenRecent', Menu); + if (menubarOpenRecent == null) throw "Could not find menubarOpenRecent!"; + // Setup notifications. @:privateAccess - // NotificationManager.GUTTER_SIZE = 56; NotificationManager.GUTTER_SIZE = 20; } @@ -1886,10 +1990,13 @@ class ChartEditorState extends HaxeUIState { saveDataDirty = false; - // Auto-save the chart. + // 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); @@ -3835,7 +3942,7 @@ class ChartEditorState extends HaxeUIState commandHistoryDirty = false; // Update the Undo and Redo buttons. - var undoButton:Null<MenuItem> = findComponent('menubarItemUndo', MenuItem); + var undoButton:Null<FunkinMenuItem> = findComponent('menubarItemUndo', FunkinMenuItem); if (undoButton != null) { @@ -3857,7 +3964,7 @@ class ChartEditorState extends HaxeUIState trace('undoButton is null'); } - var redoButton:Null<MenuItem> = findComponent('menubarItemRedo', MenuItem); + var redoButton:Null<FunkinMenuItem> = findComponent('menubarItemRedo', FunkinMenuItem); if (redoButton != null) { diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index edd95f946..d37505b4c 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -391,6 +391,11 @@ class Constants */ public static final GHOST_TAPPING:Bool = false; + /** + * 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. */ diff --git a/source/funkin/util/FileUtil.hx b/source/funkin/util/FileUtil.hx index 72c9c43f1..5fae983d4 100644 --- a/source/funkin/util/FileUtil.hx +++ b/source/funkin/util/FileUtil.hx @@ -344,13 +344,22 @@ class FileUtil public static function readBytesFromPath(path:String):Bytes { #if sys - if (!sys.FileSystem.exists(path)) return null; + if (!doesFileExist(path)) return null; return sys.io.File.getBytes(path); #else return null; #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. * Works great on HTML5 or desktop. @@ -434,7 +443,7 @@ class FileUtil case Force: sys.io.File.saveContent(path, data); case Skip: - if (!sys.FileSystem.exists(path)) + if (!doesFileExist(path)) { sys.io.File.saveContent(path, data); } @@ -443,7 +452,7 @@ class FileUtil throw 'File already exists: $path'; } case Ask: - if (sys.FileSystem.exists(path)) + if (doesFileExist(path)) { // TODO: We don't have the technology to use native popups yet. } @@ -475,7 +484,7 @@ class FileUtil case Force: sys.io.File.saveBytes(path, data); case Skip: - if (!sys.FileSystem.exists(path)) + if (!doesFileExist(path)) { sys.io.File.saveBytes(path, data); } @@ -484,7 +493,7 @@ class FileUtil throw 'File already exists: $path'; } case Ask: - if (sys.FileSystem.exists(path)) + if (doesFileExist(path)) { // TODO: We don't have the technology to use native popups yet. } @@ -523,7 +532,7 @@ class FileUtil public static function createDirIfNotExists(dir:String):Void { #if sys - if (!sys.FileSystem.exists(dir)) + if (!doesFileExist(dir)) { sys.FileSystem.createDirectory(dir); }