From 20e6c7a2be3ac8c23f736cc2b247d7ebab5b5c00 Mon Sep 17 00:00:00 2001 From: EliteMasterEric <ericmyllyoja@gmail.com> Date: Tue, 28 Feb 2023 13:17:28 -0500 Subject: [PATCH] Fix 5 or 6 issues with charting --- Project.xml | 6 +- haxe_libraries/README.md | 3 + .../charting/ChartEditorDialogHandler.hx | 250 +++++++++--------- .../debug/charting/ChartEditorNoteSprite.hx | 24 +- .../ui/debug/charting/ChartEditorState.hx | 84 +++--- source/module.xml | 9 +- 6 files changed, 206 insertions(+), 170 deletions(-) create mode 100644 haxe_libraries/README.md diff --git a/Project.xml b/Project.xml index ef58d5b53..393248698 100644 --- a/Project.xml +++ b/Project.xml @@ -156,9 +156,13 @@ <haxeflag name="--macro" value="include('funkin')" /> <!-- Ensure all UI components are available at runtime. --> + <haxeflag name="--macro" value="include('haxe.ui.backend.flixel.components')" /> + <haxeflag name="--macro" value="include('haxe.ui.containers.dialogs')" /> + <haxeflag name="--macro" value="include('haxe.ui.containers.menus')" /> + <haxeflag name="--macro" value="include('haxe.ui.containers.properties')" /> + <haxeflag name="--macro" value="include('haxe.ui.core')" /> <haxeflag name="--macro" value="include('haxe.ui.components')" /> <haxeflag name="--macro" value="include('haxe.ui.containers')" /> - <haxeflag name="--macro" value="include('haxe.ui.containers.menus')" /> <!-- Ensure additional class packages are available at runtime (some only really used by scripts). diff --git a/haxe_libraries/README.md b/haxe_libraries/README.md new file mode 100644 index 000000000..8ea199c6d --- /dev/null +++ b/haxe_libraries/README.md @@ -0,0 +1,3 @@ +# haxe_libraries + +Used by Lix \ No newline at end of file diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index 99b03381e..cecbbfb64 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -1,5 +1,6 @@ package funkin.ui.debug.charting; +import haxe.io.Path; import flixel.FlxSprite; import flixel.util.FlxTimer; import funkin.input.Cursor; @@ -8,6 +9,7 @@ import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.song.SongData.SongDataParser; import funkin.play.song.SongData.SongPlayableChar; import funkin.play.song.SongData.SongTimeChange; +import haxe.ui.core.Component; import haxe.ui.components.Button; import haxe.ui.components.DropDown; import haxe.ui.components.Image; @@ -18,7 +20,6 @@ import haxe.ui.components.TextField; import haxe.ui.containers.Box; import haxe.ui.containers.dialogs.Dialog; import haxe.ui.containers.dialogs.Dialogs; -import haxe.ui.containers.properties.Property; import haxe.ui.containers.properties.PropertyGrid; import haxe.ui.containers.properties.PropertyGroup; import haxe.ui.containers.VBox; @@ -27,16 +28,19 @@ import haxe.ui.events.UIEvent; using Lambda; +/** + * Handles dialogs for the new Chart Editor. + */ class ChartEditorDialogHandler { - static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT = Paths.ui('chart-editor/dialogs/about'); - static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT = Paths.ui('chart-editor/dialogs/welcome'); - static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT = Paths.ui('chart-editor/dialogs/upload-inst'); - static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT = Paths.ui('chart-editor/dialogs/song-metadata'); - static final CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT = Paths.ui('chart-editor/dialogs/song-metadata-chargroup'); - static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT = Paths.ui('chart-editor/dialogs/upload-vocals'); - static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT = Paths.ui('chart-editor/dialogs/upload-vocals-entry'); - static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT = Paths.ui('chart-editor/dialogs/user-guide'); + static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT:String = Paths.ui('chart-editor/dialogs/about'); + static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT:String = Paths.ui('chart-editor/dialogs/welcome'); + static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-inst'); + static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata'); + static final CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata-chargroup'); + static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals'); + static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals-entry'); + static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide'); /** * @@ -55,55 +59,23 @@ class ChartEditorDialogHandler // TODO: Add callbacks to the dialog buttons - // Switch the graphic for frames. - var bfSpritePlaceholder:Image = dialog.findComponent('bfSprite', Image); - - // TODO: Replace this bullshit with a custom HaxeUI component that loads the sprite from the game's assets. - - if (bfSpritePlaceholder != null) - { - var bfSprite:FlxSprite = new FlxSprite(0, 0); - - bfSprite.visible = false; - - var frames = Paths.getSparrowAtlas(bfSpritePlaceholder.resource); - bfSprite.frames = frames; - - bfSprite.animation.addByPrefix('idle', 'Boyfriend DJ0', 24, true); - bfSprite.animation.play('idle'); - - bfSpritePlaceholder.rootComponent.add(bfSprite); - bfSpritePlaceholder.visible = false; - - new FlxTimer().start(0.10, (_timer:FlxTimer) -> - { - bfSprite.x = bfSpritePlaceholder.screenLeft; - bfSprite.y = bfSpritePlaceholder.screenTop; - bfSprite.setGraphicSize(Std.int(bfSpritePlaceholder.width), Std.int(bfSpritePlaceholder.height)); - bfSprite.visible = true; - }); - } - // Add handlers to the "Create From Song" section. var linkCreateBasic:Link = dialog.findComponent('splashCreateFromSongBasic', Link); - linkCreateBasic.onClick = (_event) -> - { + linkCreateBasic.onClick = (_event) -> { dialog.hideDialog(DialogButton.CANCEL); // Create song wizard var uploadInstDialog = openUploadInstDialog(state, false); - uploadInstDialog.onDialogClosed = (_event) -> - { + uploadInstDialog.onDialogClosed = (_event) -> { state.isHaxeUIDialogOpen = false; if (_event.button == DialogButton.APPLY) { var songMetadataDialog = openSongMetadataDialog(state); - songMetadataDialog.onDialogClosed = (_event) -> - { + songMetadataDialog.onDialogClosed = (_event) -> { state.isHaxeUIDialogOpen = false; if (_event.button == DialogButton.APPLY) { - var uploadVocalsDialog = openUploadVocalsDialog(state); + var uploadVocalsDialog = openUploadVocalsDialog(state, false); } }; } @@ -145,8 +117,7 @@ class ChartEditorDialogHandler var linkTemplateSong:Link = new Link(); linkTemplateSong.text = songName; - linkTemplateSong.onClick = (_event) -> - { + linkTemplateSong.onClick = (_event) -> { dialog.hideDialog(DialogButton.CANCEL); // Load song from template @@ -165,72 +136,111 @@ class ChartEditorDialogHandler var instrumentalBox:Box = dialog.findComponent('instrumentalBox', Box); - instrumentalBox.onMouseOver = (_event) -> - { + instrumentalBox.onMouseOver = (_event) -> { instrumentalBox.swapClass('upload-bg', 'upload-bg-hover'); Cursor.cursorMode = Pointer; } - instrumentalBox.onMouseOut = (_event) -> - { + instrumentalBox.onMouseOut = (_event) -> { instrumentalBox.swapClass('upload-bg-hover', 'upload-bg'); Cursor.cursorMode = Default; } var onDropFile:String->Void; - instrumentalBox.onClick = (_event) -> - { + instrumentalBox.onClick = (_event) -> { Dialogs.openBinaryFile("Open Instrumental", [ - {label: "Audio File (.ogg)", extension: "ogg"}], function(selectedFile) - { - if (selectedFile != null) - { - trace('Selected file: ' + selectedFile); - state.loadInstrumentalFromBytes(selectedFile.bytes); - dialog.hideDialog(DialogButton.APPLY); - removeDropHandler(onDropFile); - } + {label: "Audio File (.ogg)", extension: "ogg"}], function(selectedFile) { + if (selectedFile != null) + { + trace('Selected file: ' + selectedFile); + state.loadInstrumentalFromBytes(selectedFile.bytes); + dialog.hideDialog(DialogButton.APPLY); + removeDropHandler(onDropFile); + } }); } - onDropFile = (path:String) -> - { + onDropFile = (path:String) -> { trace('Dropped file: ' + path); state.loadInstrumentalFromPath(path); dialog.hideDialog(DialogButton.APPLY); removeDropHandler(onDropFile); }; - addDropHandler(onDropFile); + addDropHandler(instrumentalBox, onDropFile); return dialog; } - static function addDropHandler(handler:String->Void) + static var dropHandlers:Array< + { + component:Component, + handler:(String->Void) + }> = []; + + static function addDropHandler(component:Component, handler:String->Void):Void { #if desktop - FlxG.stage.window.onDropFile.add(handler); + if (!FlxG.stage.window.onDropFile.has(onDropFile)) FlxG.stage.window.onDropFile.add(onDropFile); + + dropHandlers.push( + { + component: component, + handler: handler + }); #else trace('addDropHandler not implemented for this platform'); #end } - static function removeDropHandler(handler:String->Void) + static function removeDropHandler(handler:String->Void):Void { #if desktop FlxG.stage.window.onDropFile.remove(handler); #end } + static function clearDropHandlers():Void + { + #if desktop + dropHandlers = []; + FlxG.stage.window.onDropFile.remove(onDropFile); + #end + } + + static function onDropFile(path:String):Void + { + // a VERY short timer to wait for the mouse position to update + new FlxTimer().start(0.01, function(_) { + trace("mouseX: " + FlxG.mouse.screenX + ", mouseY: " + FlxG.mouse.screenY); + + for (handler in dropHandlers) + { + if (handler.component.hitTest(FlxG.mouse.screenX, FlxG.mouse.screenY)) + { + trace('File dropped on component! ' + handler.component.id); + handler.handler(path); + return; + } + } + + trace('File dropped on nothing!' + path); + }); + } + + /** + * Opens the dialog in the wizard where the user can set song metadata like name and artist and BPM. + * @param state The ChartEditorState instance. + * @return The dialog to open. + */ public static function openSongMetadataDialog(state:ChartEditorState):Dialog { var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false); var dialogSongName:TextField = dialog.findComponent('dialogSongName', TextField); - dialogSongName.onChange = (event:UIEvent) -> - { - var valid = event.target.text != null && event.target.text != ""; + dialogSongName.onChange = function(event:UIEvent) { + var valid:Bool = event.target.text != null && event.target.text != ''; if (valid) { @@ -245,9 +255,8 @@ class ChartEditorDialogHandler state.currentSongMetadata.songName = null; var dialogSongArtist:TextField = dialog.findComponent('dialogSongArtist', TextField); - dialogSongArtist.onChange = (event:UIEvent) -> - { - var valid = event.target.text != null && event.target.text != ""; + dialogSongArtist.onChange = function(event:UIEvent) { + var valid:Bool = event.target.text != null && event.target.text != ''; if (valid) { @@ -262,8 +271,7 @@ class ChartEditorDialogHandler state.currentSongMetadata.artist = null; var dialogStage:DropDown = dialog.findComponent('dialogStage', DropDown); - dialogStage.onChange = (event:UIEvent) -> - { + dialogStage.onChange = function(event:UIEvent) { var valid = event.data != null && event.data.id != null; if (event.data.id == null) return; @@ -272,16 +280,14 @@ class ChartEditorDialogHandler state.currentSongMetadata.playData.stage = null; var dialogNoteSkin:DropDown = dialog.findComponent('dialogNoteSkin', DropDown); - dialogNoteSkin.onChange = (event:UIEvent) -> - { + dialogNoteSkin.onChange = (event:UIEvent) -> { if (event.data.id == null) return; state.currentSongMetadata.playData.noteSkin = event.data.id; }; state.currentSongMetadata.playData.noteSkin = null; var dialogBPM:NumberStepper = dialog.findComponent('dialogBPM', NumberStepper); - dialogBPM.onChange = (event:UIEvent) -> - { + dialogBPM.onChange = (event:UIEvent) -> { if (event.value == null || event.value <= 0) return; var timeChanges = state.currentSongMetadata.timeChanges; @@ -301,11 +307,9 @@ class ChartEditorDialogHandler var dialogCharGrid:PropertyGrid = dialog.findComponent('dialogCharGrid', PropertyGrid); var dialogCharAdd:Button = dialog.findComponent('dialogCharAdd', Button); - dialogCharAdd.onClick = (_event) -> - { + dialogCharAdd.onClick = (_event) -> { var charGroup:PropertyGroup; - charGroup = buildCharGroup(state, null, () -> - { + charGroup = buildCharGroup(state, null, () -> { dialogCharGrid.removeComponent(charGroup); }); dialogCharGrid.addComponent(charGroup); @@ -317,8 +321,7 @@ class ChartEditorDialogHandler dialogCharGrid.addComponent(buildCharGroup(state, 'bf', null)); var dialogContinue:Button = dialog.findComponent('dialogContinue', Button); - dialogContinue.onClick = (_event) -> - { + dialogContinue.onClick = (_event) -> { dialog.hideDialog(DialogButton.APPLY); }; @@ -329,8 +332,7 @@ class ChartEditorDialogHandler { var groupKey = key; - var getCharData = () -> - { + var getCharData = () -> { if (groupKey == null) groupKey = 'newChar${state.currentSongMetadata.playData.playableChars.keys().count()}'; var result = state.currentSongMetadata.playData.playableChars.get(groupKey); @@ -342,16 +344,14 @@ class ChartEditorDialogHandler return result; } - var moveCharGroup = (target:String) -> - { + var moveCharGroup = (target:String) -> { var charData = getCharData(); state.currentSongMetadata.playData.playableChars.remove(groupKey); state.currentSongMetadata.playData.playableChars.set(target, charData); groupKey = target; } - var removeGroup = () -> - { + var removeGroup = () -> { state.currentSongMetadata.playData.playableChars.remove(groupKey); removeFunc(); } @@ -361,8 +361,7 @@ class ChartEditorDialogHandler var charGroup:PropertyGroup = cast state.buildComponent(CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT); var charGroupPlayer:DropDown = charGroup.findComponent('charGroupPlayer', DropDown); - charGroupPlayer.onChange = (event:UIEvent) -> - { + charGroupPlayer.onChange = (event:UIEvent) -> { charGroup.text = event.data.text; moveCharGroup(event.data.id); }; @@ -374,22 +373,19 @@ class ChartEditorDialogHandler } var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown); - charGroupOpponent.onChange = (event:UIEvent) -> - { + charGroupOpponent.onChange = (event:UIEvent) -> { charData.opponent = event.data.id; }; charGroupOpponent.value = getCharData().opponent; var charGroupGirlfriend:DropDown = charGroup.findComponent('charGroupGirlfriend', DropDown); - charGroupGirlfriend.onChange = (event:UIEvent) -> - { + charGroupGirlfriend.onChange = (event:UIEvent) -> { charData.girlfriend = event.data.id; }; charGroupGirlfriend.value = getCharData().girlfriend; var charGroupRemove:Button = charGroup.findComponent('charGroupRemove', Button); - charGroupRemove.onClick = (_event:MouseEvent) -> - { + charGroupRemove.onClick = (_event:MouseEvent) -> { removeGroup(); }; @@ -413,7 +409,11 @@ class ChartEditorDialogHandler var dialogContainer = dialog.findComponent('vocalContainer'); - var onDropFile:String->Void; + var dialogNoVocals:Button = dialog.findComponent('dialogNoVocals', Button); + dialogNoVocals.onClick = function(_event) { + // Dismiss + dialog.hideDialog(DialogButton.APPLY); + }; for (charKey in charIdsForVocals) { @@ -426,39 +426,46 @@ class ChartEditorDialogHandler var vocalsEntryLabel:Label = vocalsEntry.findComponent('vocalsEntryLabel', Label); vocalsEntryLabel.text = 'Click to browse for a vocal track for $charName.'; - vocalsEntry.onClick = (_event) -> - { + var onDropFile:String->Void = function(fullPath:String) { + trace('Selected file: $fullPath'); + var directory:String = Path.directory(fullPath); + var filename:String = Path.withoutDirectory(directory); + + vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${filename}'; + state.loadVocalsFromPath(fullPath, charKey); + dialogNoVocals.hidden = true; + removeDropHandler(onDropFile); + }; + + vocalsEntry.onClick = function(_event) { Dialogs.openBinaryFile('Open $charName Vocals', [ - {label: "Audio File (.ogg)", extension: "ogg"}], function(selectedFile) - { - if (selectedFile != null) - { - trace('Selected file: ' + selectedFile.name + "~" + selectedFile.fullPath); - vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}'; - state.loadVocalsFromBytes(selectedFile.bytes); - removeDropHandler(onDropFile); - } + {label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile) { + if (selectedFile != null) + { + trace('Selected file: ' + selectedFile.name); + vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}'; + state.loadVocalsFromBytes(selectedFile.bytes, charKey); + dialogNoVocals.hidden = true; + removeDropHandler(onDropFile); + } }); + + // onDropFile + addDropHandler(vocalsEntry, onDropFile); } dialogContainer.addComponent(vocalsEntry); } var dialogContinue:Button = dialog.findComponent('dialogContinue', Button); - dialogContinue.onClick = (_event) -> - { + dialogContinue.onClick = function(_event) { + // Dismiss dialog.hideDialog(DialogButton.APPLY); }; // TODO: Redo the logic for file drop handler to be more robust. // We need to distinguish which component the mouse is over when the file is dropped. - onDropFile = (path:String) -> - { - trace('Dropped file: ' + path); - }; - addDropHandler(onDropFile); - return dialog; } @@ -483,8 +490,7 @@ class ChartEditorDialogHandler dialog.showDialog(modal); state.isHaxeUIDialogOpen = true; - dialog.onDialogClosed = (_event) -> - { + dialog.onDialogClosed = (_event) -> { state.isHaxeUIDialogOpen = false; }; diff --git a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx index 85a1f86a2..a20b43dbd 100644 --- a/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx +++ b/source/funkin/ui/debug/charting/ChartEditorNoteSprite.hx @@ -1,7 +1,6 @@ package funkin.ui.debug.charting; import flixel.FlxObject; -import flixel.FlxBasic; import flixel.FlxSprite; import flixel.graphics.frames.FlxFramesCollection; import flixel.graphics.frames.FlxTileFrames; @@ -14,6 +13,14 @@ import funkin.play.song.SongData.SongNoteData; */ class ChartEditorNoteSprite extends FlxSprite { + /** + * The list of available note skin to validate against. + */ + public static final NOTE_STYLES:Array<String> = ['Normal', 'Pixel']; + + /** + * The ChartEditorState this note belongs to. + */ public var parentState:ChartEditorState; /** @@ -22,6 +29,11 @@ class ChartEditorNoteSprite extends FlxSprite */ public var noteData(default, set):SongNoteData; + /** + * The name of the note style currently in use. + */ + public var noteStyle(get, null):String; + /** * This note is the previous sprite in a sustain chain. */ @@ -222,14 +234,20 @@ class ChartEditorNoteSprite extends FlxSprite return this.childNoteSprite; } - public function playNoteAnimation() + function get_noteStyle():String + { + // Fall back to 'Normal' if it's not a valid note style. + return if (NOTE_STYLES.contains(this.parentState.currentSongNoteSkin)) this.parentState.currentSongNoteSkin else 'Normal'; + } + + public function playNoteAnimation():Void { // Decide whether to display a note or a sustain. var baseAnimationName:String = 'tap'; if (this.parentNoteSprite != null) baseAnimationName = (this.childNoteSprite != null) ? 'hold' : 'holdEnd'; // Play the appropriate animation for the type, direction, and skin. - var animationName = '${baseAnimationName}${this.noteData.getDirectionName()}${this.parentState.currentSongNoteSkin}'; + var animationName:String = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteStyle}'; this.animation.play(animationName); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 935e80228..a1bda9fcd 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1,5 +1,7 @@ package funkin.ui.debug.charting; +import haxe.ui.notifications.NotificationType; +import haxe.ui.notifications.NotificationManager; import haxe.DynamicAccess; import haxe.io.Path; import flixel.addons.display.FlxSliceSprite; @@ -92,6 +94,9 @@ class ChartEditorState extends HaxeUIState static final CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT = Paths.ui('chart-editor/toolbox/player-preview'); static final CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT = Paths.ui('chart-editor/toolbox/opponent-preview'); + // Validation + static final SUPPORTED_MUSIC_FORMATS:Array<String> = ['ogg']; + /** * The base grid size for the chart editor. */ @@ -124,9 +129,9 @@ class ChartEditorState extends HaxeUIState static final GRID_TOP_PAD:Int = 8; /** - * Duration, in seconds, until toast notifications are automatically hidden. + * Duration, in milliseconds, until toast notifications are automatically hidden. */ - static final NOTIFICATION_DISMISS_TIME:Float = 3.0; + static final NOTIFICATION_DISMISS_TIME:Int = 5000; // Start performing rapid undo after this many seconds. static final RAPID_UNDO_DELAY:Float = 0.4; @@ -898,7 +903,6 @@ class ChartEditorState extends HaxeUIState var renderedSelectionSquares:FlxTypedSpriteGroup<FlxSprite>; - var notifBar:SideBar; var playbarHead:Slider; public function new() @@ -1090,9 +1094,6 @@ class ChartEditorState extends HaxeUIState function buildAdditionalUI():Void { - notifBar = cast buildComponent(CHART_EDITOR_NOTIFBAR_LAYOUT); - add(notifBar); - playbarHeadLayout = buildComponent(CHART_EDITOR_PLAYBARHEAD_LAYOUT); playbarHeadLayout.width = FlxG.width - 8; @@ -1281,7 +1282,7 @@ class ChartEditorState extends HaxeUIState if (audioInstTrack != null) audioInstTrack.pitch = pitch; if (audioVocalTrackGroup != null) audioVocalTrackGroup.pitch = pitch; #end - playbackSpeedLabel.text = 'Playback Speed - ${Std.int(event.value * 100) / 100}x'; + playbackSpeedLabel.text = 'Playback Speed - ${Std.int(pitch * 100) / 100}x'; }); addUIChangeListener('menubarItemToggleToolboxTools', (event:UIEvent) -> { @@ -1340,7 +1341,7 @@ class ChartEditorState extends HaxeUIState #end } - function onWindowClose(exitCode:Int) + function onWindowClose(exitCode:Int):Void { trace('Window exited with exit code: $exitCode'); trace('Should save chart? $saveDataDirty'); @@ -1351,12 +1352,12 @@ class ChartEditorState extends HaxeUIState } } - function cleanupAutoSave() + function cleanupAutoSave():Void { WindowUtil.windowExit.remove(onWindowClose); } - public override function update(elapsed:Float) + public override function update(elapsed:Float):Void { // dispatchEvent gets called here. super.update(elapsed); @@ -1387,10 +1388,16 @@ class ChartEditorState extends HaxeUIState #if debug if (FlxG.keys.justPressed.F) { - // This breaks the layout don't use it. - // showNotification('Hi there :)'); - - // autoSave(); + NotificationManager.instance.addNotification( + { + title: 'This is a Notification', + body: 'Hello, world!', + type: NotificationType.Info, + expiryMs: NOTIFICATION_DISMISS_TIME + // styleNames: 'cssStyleName', + // icon: 'assetPath', + // actions: ['action1', 'action2'] + }); } if (FlxG.keys.justPressed.E) @@ -1416,7 +1423,7 @@ class ChartEditorState extends HaxeUIState // dispatchEvent gets called here. if (!super.beatHit()) return false; - if (shouldPlayMetronome && audioInstTrack.playing) + if (shouldPlayMetronome && (audioInstTrack != null && audioInstTrack.playing)) { playMetronomeTick(Conductor.currentBeat % 4 == 0); } @@ -1432,7 +1439,7 @@ class ChartEditorState extends HaxeUIState // dispatchEvent gets called here. if (!super.stepHit()) return false; - if (audioInstTrack.playing) + if (audioInstTrack != null && audioInstTrack.playing) { healthIconDad.onStepHit(Conductor.currentStep); healthIconBF.onStepHit(Conductor.currentStep); @@ -1447,7 +1454,7 @@ class ChartEditorState extends HaxeUIState /** * Handle keybinds for scrolling the chart editor grid. **/ - function handleScrollKeybinds() + function handleScrollKeybinds():Void { // Don't scroll when the cursor is over the UI. if (isCursorOverHaxeUI) return; @@ -1456,16 +1463,17 @@ class ChartEditorState extends HaxeUIState var scrollAmount:Float = 0; // Amount to scroll the playhead relative to the grid. var playheadAmount:Float = 0; + var shouldPause:Bool = false; // Up Arrow = Scroll Up if (FlxG.keys.justPressed.UP) { - scrollAmount = -GRID_SIZE * 0.25; + scrollAmount = -GRID_SIZE * 0.25 * 5; } // Down Arrow = Scroll Down if (FlxG.keys.justPressed.DOWN) { - scrollAmount = GRID_SIZE * 0.25; + scrollAmount = GRID_SIZE * 0.25 * 5; } // PAGE UP = Jump Up 1 Measure @@ -2089,7 +2097,7 @@ class ChartEditorState extends HaxeUIState /** * Handle using `renderedNotes` to display notes from `currentSongChartNoteData`. */ - function handleNoteDisplay() + function handleNoteDisplay():Void { if (noteDisplayDirty) { @@ -2963,10 +2971,20 @@ class ChartEditorState extends HaxeUIState /** * Loads an instrumental from an absolute file path, replacing the current instrumental. + * + * @param path The absolute path to the audio file. */ public function loadInstrumentalFromPath(path:String):Void { #if sys + // Validate file extension. + var fileExtension:String = Path.extension(path); + if (!SUPPORTED_MUSIC_FORMATS.contains(fileExtension)) + { + trace('[WARN] Unsupported file extension: $fileExtension'); + return; + } + var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path); loadInstrumentalFromBytes(fileBytes); #else @@ -3025,17 +3043,17 @@ class ChartEditorState extends HaxeUIState /** * Loads a vocal track from an absolute file path. */ - public function loadVocalsFromPath(path:String):Void + public function loadVocalsFromPath(path:String, ?charKey:String):Void { #if sys var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path); - loadVocalsFromBytes(fileBytes); + loadVocalsFromBytes(fileBytes, charKey); #else trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way."); #end } - public function loadVocalsFromAsset(path:String):Void + public function loadVocalsFromAsset(path:String, ?charKey:String):Void { var vocalTrack:FlxSound = FlxG.sound.load(path, 1.0, false); audioVocalTrackGroup.add(vocalTrack); @@ -3044,7 +3062,7 @@ class ChartEditorState extends HaxeUIState /** * Loads a vocal track from audio byte data. */ - public function loadVocalsFromBytes(bytes:haxe.io.Bytes):Void + public function loadVocalsFromBytes(bytes:haxe.io.Bytes, ?charKey:String):Void { var openflSound = new openfl.media.Sound(); openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length); @@ -3065,7 +3083,7 @@ class ChartEditorState extends HaxeUIState if (song == null) { - // showNotification('Failed to load song template.'); + // showNotification('Failed to load song.'); return; } @@ -3218,24 +3236,12 @@ class ChartEditorState extends HaxeUIState ChartEditorNoteSprite.noteFrameCollection = null; } - /** - * Displays a notification to the user. The only action is to dismiss. - */ - function showNotification(text:String) - { - // Make it appear. - notifBar.show(); - - // Auto dismiss. - new FlxTimer().start(NOTIFICATION_DISMISS_TIME, (_:FlxTimer) -> dismissNotification()); - } - /** * Dismiss any existing notifications, if there are any. */ - function dismissNotification():Void + function dismissNotifications():Void { - notifBar.hide(); + NotificationManager.instance.clearNotifications(); } /** diff --git a/source/module.xml b/source/module.xml index fcedcb346..dd7c9ad20 100644 --- a/source/module.xml +++ b/source/module.xml @@ -7,14 +7,13 @@ This needs to be done HERE and not via the `include` macro because `Toolkit.init()` reads this to build the component registry. --> - <class package="haxe.ui.core" loadAll="true" /> - + <class package="haxe.ui.backend.flixel.components" loadAll="true" /> <class package="haxe.ui.components" loadAll="true" /> - - <class package="haxe.ui.containers" loadAll="true" /> - <class package="haxe.ui.containers.menus" loadAll="true" /> <class package="haxe.ui.containers.dialogs" loadAll="true" /> + <class package="haxe.ui.containers.menus" loadAll="true" /> <class package="haxe.ui.containers.properties" loadAll="true" /> + <class package="haxe.ui.containers" loadAll="true" /> + <class package="haxe.ui.core" loadAll="true" /> <!-- Custom components. --> <class package="funkin.ui.haxeui.components" loadAll="true" />