From 95b43cce552df64b4654fe4e63918669e96c3016 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 21 Nov 2023 02:12:34 -0500 Subject: [PATCH] Several UI tweaks and usability fixes. (#224) Co-authored-by: Cameron Taylor --- assets | 2 +- .../ui/debug/charting/ChartEditorState.hx | 66 ++++++++++--- .../handlers/ChartEditorDialogHandler.hx | 92 ++++++++++++++----- .../ChartEditorImportExportHandler.hx | 7 +- source/funkin/ui/haxeui/HaxeUIState.hx | 14 +++ 5 files changed, 143 insertions(+), 38 deletions(-) diff --git a/assets b/assets index c3ce920f1..4ed2b3084 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit c3ce920f162ad53cb510557b3bc69ab9805f48d7 +Subproject commit 4ed2b3084d54899e10d10a97eaafe210158768be diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 0349db550..a3aeccc02 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1,5 +1,6 @@ package funkin.ui.debug.charting; +import haxe.ui.containers.menus.MenuBar; import flixel.addons.display.FlxSliceSprite; import flixel.addons.display.FlxTiledSprite; import flixel.addons.transition.FlxTransitionableState; @@ -565,6 +566,23 @@ class ChartEditorState extends HaxeUIState return FocusManager.instance.focus != null; } + /** + * Whether the user's mouse cursor is hovering over a SOLID component of the HaxeUI. + * If so, we can ignore certain mouse events underneath. + */ + var isCursorOverHaxeUI(get, never):Bool; + + function get_isCursorOverHaxeUI():Bool + { + return Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.screenX, FlxG.mouse.screenY); + } + + /** + * The value of `isCursorOverHaxeUI` from the previous frame. + * This is useful because we may have just clicked a menu item, causing the menu to disappear. + */ + var wasCursorOverHaxeUI:Bool = false; + /** * Set by ChartEditorDialogHandler, used to prevent background interaction while the dialog is open. */ @@ -1009,7 +1027,7 @@ class ChartEditorState extends HaxeUIState { // Initialize to the default value if not set. result = []; - trace('Initializing blank note data for difficulty ' + selectedDifficulty); + trace('Initializing blank chart for difficulty ' + selectedDifficulty); currentSongChartData.notes.set(selectedDifficulty, result); currentSongMetadata.playData.difficulties.pushUnique(selectedDifficulty); return result; @@ -1941,8 +1959,10 @@ class ChartEditorState extends HaxeUIState playbarHead.onDragEnd = function(_:DragEvent) { playbarHeadDragging = false; + var value:Null = playbarHead?.value; + // Set the song position to where the playhead was moved to. - scrollPositionInPixels = songLengthInPixels * (playbarHead?.value ?? 0 / 100); + scrollPositionInPixels = songLengthInPixels * ((value ?? 0.0) / 100); // Update the conductor and audio tracks to match. moveSongToScrollPosition(); @@ -1963,6 +1983,10 @@ class ChartEditorState extends HaxeUIState menubarItemSaveChart = findComponent('menubarItemSaveChart', MenuItem); if (menubarItemSaveChart == null) throw "Could not find menubarItemSaveChart!"; + var menubar = findComponent('menubar', MenuBar); + if (menubar == null) throw "Could not find menubar!"; + if (!Preferences.debugDisplay) menubar.paddingLeft = null; + // Setup notifications. @:privateAccess NotificationManager.GUTTER_SIZE = 20; @@ -2280,6 +2304,8 @@ class ChartEditorState extends HaxeUIState #if debug handleQuickWatch(); #end + + handlePostUpdate(); } /** @@ -2724,7 +2750,7 @@ class ChartEditorState extends HaxeUIState function handleScrollKeybinds():Void { // Don't scroll when the user is interacting with the UI, unless a playbar button (the << >> ones) is pressed. - if (isHaxeUIFocused && playbarButtonPressed == null) return; + if ((isHaxeUIFocused || isCursorOverHaxeUI) && playbarButtonPressed == null) return; var scrollAmount:Float = 0; // Amount to scroll the grid. var playheadAmount:Float = 0; // Amount to scroll the playhead relative to the grid. @@ -2925,7 +2951,7 @@ class ChartEditorState extends HaxeUIState if (FlxG.mouse.justReleased) FlxG.sound.play(Paths.sound("chartingSounds/ClickUp")); // Note: If a menu is open in HaxeUI, don't handle cursor behavior. - var shouldHandleCursor:Bool = !isHaxeUIFocused + var shouldHandleCursor:Bool = !(isHaxeUIFocused || playbarHeadDragging) || (selectionBoxStartPos != null) || (dragTargetNote != null || dragTargetEvent != null); var eventColumn:Int = (STRUMLINE_SIZE * 2 + 1) - 1; @@ -3095,9 +3121,9 @@ class ChartEditorState extends HaxeUIState if (!FlxG.keys.pressed.CONTROL) { // Deselect all items. - if (currentNoteSelection.length > 0 || currentEventSelection.length > 0) + var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0); + if (shouldDeselect) { - trace('Clicked and dragged outside grid, deselecting all items.'); performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); } } @@ -3213,7 +3239,11 @@ class ChartEditorState extends HaxeUIState else { // Click on an empty space to deselect everything. - performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0); + if (shouldDeselect) + { + performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); + } } } } @@ -3224,9 +3254,9 @@ class ChartEditorState extends HaxeUIState if (!FlxG.keys.pressed.CONTROL) { // Deselect all items. - if (currentNoteSelection.length > 0 || currentEventSelection.length > 0) + var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0); + if (shouldDeselect) { - trace('Clicked outside grid, deselecting all items.'); performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)); } } @@ -3295,7 +3325,6 @@ class ChartEditorState extends HaxeUIState if (FlxG.mouse.screenY < MENU_BAR_HEIGHT) { // Scroll up. - trace('Scroll up!'); var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.screenY; scrollPositionInPixels -= diff * 0.5; // Too fast! moveSongToScrollPosition(); @@ -3303,7 +3332,6 @@ class ChartEditorState extends HaxeUIState else if (FlxG.mouse.screenY > (playbarHeadLayout?.y ?? 0.0)) { // Scroll down. - trace('Scroll down!'); var diff:Float = FlxG.mouse.screenY - (playbarHeadLayout?.y ?? 0.0); scrollPositionInPixels += diff * 0.5; // Too fast! moveSongToScrollPosition(); @@ -3454,7 +3482,6 @@ class ChartEditorState extends HaxeUIState if (isNoteSelected(highlightedNote.noteData)) { // Clicked a selected event, start dragging. - trace('Ready to drag!'); dragTargetNote = highlightedNote; } else @@ -3468,7 +3495,6 @@ class ChartEditorState extends HaxeUIState if (isEventSelected(highlightedEvent.eventData)) { // Clicked a selected event, start dragging. - trace('Ready to drag!'); dragTargetEvent = highlightedEvent; } else @@ -3682,6 +3708,7 @@ class ChartEditorState extends HaxeUIState for (curVariation in availableVariations) { + trace('DIFFICULTY TOOLBOX: Variation ${curVariation}'); var variationMetadata:Null = songMetadata.get(curVariation); if (variationMetadata == null) continue; @@ -3696,6 +3723,7 @@ class ChartEditorState extends HaxeUIState for (difficulty in difficultyList) { + trace('DIFFICULTY TOOLBOX: Difficulty ${curVariation}_$difficulty'); var _treeDifficulty:TreeViewNode = treeVariation.addNode( { id: 'stv_difficulty_${curVariation}_$difficulty', @@ -4042,6 +4070,13 @@ class ChartEditorState extends HaxeUIState } } + // CTRL + F = Flip Notes + if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.F) + { + // Flip selected notes. + performCommand(new FlipNotesCommand(currentNoteSelection)); + } + // CTRL + A = Select All if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.A) { @@ -4119,6 +4154,11 @@ class ChartEditorState extends HaxeUIState FlxG.watch.addQuick("eventsSelected", currentEventSelection.length); } + function handlePostUpdate():Void + { + wasCursorOverHaxeUI = isCursorOverHaxeUI; + } + /** * PLAYTEST FUNCTIONS */ diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx index e27db6e87..06da6ee12 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorDialogHandler.hx @@ -153,8 +153,8 @@ class ChartEditorDialogHandler #end // Create New Song "Easy/Normal/Hard" - var linkCreateBasic:Null = dialog.findComponent('splashCreateFromSongBasic', Link); - if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasic link in Welcome dialog'; + var linkCreateBasic:Null = dialog.findComponent('splashCreateFromSongBasicOnly', Link); + if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasicOnly link in Welcome dialog'; linkCreateBasic.onClick = function(_event) { // Hide the welcome dialog dialog.hideDialog(DialogButton.CANCEL); @@ -163,12 +163,12 @@ class ChartEditorDialogHandler // // Create Song Wizard // - openCreateSongWizardBasic(state, false); + openCreateSongWizardBasicOnly(state, false); } // Create New Song "Erect/Nightmare" - var linkCreateErect:Null = dialog.findComponent('splashCreateFromSongErect', Link); - if (linkCreateErect == null) throw 'Could not locate splashCreateFromSongErect link in Welcome dialog'; + var linkCreateErect:Null = dialog.findComponent('splashCreateFromSongErectOnly', Link); + if (linkCreateErect == null) throw 'Could not locate splashCreateFromSongErectOnly link in Welcome dialog'; linkCreateErect.onClick = function(_event) { // Hide the welcome dialog dialog.hideDialog(DialogButton.CANCEL); @@ -176,7 +176,20 @@ class ChartEditorDialogHandler // // Create Song Wizard // - openCreateSongWizardErect(state, false); + openCreateSongWizardErectOnly(state, false); + } + + // Create New Song "Easy/Normal/Hard/Erect/Nightmare" + var linkCreateErect:Null = dialog.findComponent('splashCreateFromSongBasicErect', Link); + if (linkCreateErect == null) throw 'Could not locate splashCreateFromSongBasicErect link in Welcome dialog'; + linkCreateErect.onClick = function(_event) { + // Hide the welcome dialog + dialog.hideDialog(DialogButton.CANCEL); + + // + // Create Song Wizard + // + openCreateSongWizardBasicErect(state, false); } var linkImportChartLegacy:Null = dialog.findComponent('splashImportChartLegacy', Link); @@ -458,10 +471,10 @@ class ChartEditorDialogHandler }; } - public static function openCreateSongWizardBasic(state:ChartEditorState, closable:Bool):Void + public static function openCreateSongWizardBasicOnly(state:ChartEditorState, closable:Bool):Void { // Step 1. Song Metadata - var songMetadataDialog:Dialog = openSongMetadataDialog(state); + var songMetadataDialog:Dialog = openSongMetadataDialog(state, false, Constants.DEFAULT_VARIATION); songMetadataDialog.onDialogClosed = function(_event) { state.isHaxeUIDialogOpen = false; if (_event.button == DialogButton.APPLY) @@ -497,10 +510,49 @@ class ChartEditorDialogHandler }; } - public static function openCreateSongWizardErect(state:ChartEditorState, closable:Bool):Void + public static function openCreateSongWizardErectOnly(state:ChartEditorState, closable:Bool):Void { // Step 1. Song Metadata - var songMetadataDialog:Dialog = openSongMetadataDialog(state); + var songMetadataDialog:Dialog = openSongMetadataDialog(state, true, Constants.DEFAULT_VARIATION); + songMetadataDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + if (_event.button == DialogButton.APPLY) + { + // Step 2. Upload Instrumental + var uploadInstDialog:Dialog = openUploadInstDialog(state, closable); + uploadInstDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + if (_event.button == DialogButton.APPLY) + { + // Step 3. Upload Vocals + // NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard. + var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog + uploadVocalsDialog.onDialogClosed = function(_event) { + state.isHaxeUIDialogOpen = false; + state.currentWorkingFilePath = null; // New file, so no path. + state.switchToCurrentInstrumental(); + state.postLoadInstrumental(); + } + } + else + { + // User cancelled the wizard at Step 2! Back to the welcome dialog. + openWelcomeDialog(state); + } + }; + } + else + { + // User cancelled the wizard at Step 1! Back to the welcome dialog. + openWelcomeDialog(state); + } + }; + } + + public static function openCreateSongWizardBasicErect(state:ChartEditorState, closable:Bool):Void + { + // Step 1. Song Metadata + var songMetadataDialog:Dialog = openSongMetadataDialog(state, false, Constants.DEFAULT_VARIATION); songMetadataDialog.onDialogClosed = function(_event) { state.isHaxeUIDialogOpen = false; if (_event.button == DialogButton.APPLY) @@ -517,7 +569,7 @@ class ChartEditorDialogHandler uploadVocalsDialog.onDialogClosed = function(_event) { state.switchToCurrentInstrumental(); // Step 4. Song Metadata (Erect) - var songMetadataDialogErect:Dialog = openSongMetadataDialog(state, 'erect'); + var songMetadataDialogErect:Dialog = openSongMetadataDialog(state, true, 'erect'); songMetadataDialogErect.onDialogClosed = function(_event) { state.isHaxeUIDialogOpen = false; if (_event.button == DialogButton.APPLY) @@ -699,10 +751,8 @@ class ChartEditorDialogHandler * @return The dialog to open. */ @:haxe.warning("-WVarInit") - public static function openSongMetadataDialog(state:ChartEditorState, ?targetVariation:String):Dialog + public static function openSongMetadataDialog(state:ChartEditorState, erect:Bool, targetVariation:String):Dialog { - if (targetVariation == null) targetVariation = Constants.DEFAULT_VARIATION; - var dialog:Null = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false); if (dialog == null) throw 'Could not locate Song Metadata dialog'; @@ -719,13 +769,10 @@ class ChartEditorDialogHandler dialog.hideDialog(DialogButton.CANCEL); } - var newSongMetadata:SongMetadata = new SongMetadata('', '', 'default'); + var newSongMetadata:SongMetadata = new SongMetadata('', '', Constants.DEFAULT_VARIATION); - newSongMetadata.playData.difficulties = switch (targetVariation) - { - case 'erect': ['erect', 'nightmare']; - default: ['easy', 'normal', 'hard']; - }; + newSongMetadata.variation = targetVariation; + newSongMetadata.playData.difficulties = (erect) ? ['erect', 'nightmare'] : ['easy', 'normal', 'hard']; var inputSongName:Null = dialog.findComponent('inputSongName', TextField); if (inputSongName == null) throw 'Could not locate inputSongName TextField in Song Metadata dialog'; @@ -830,11 +877,14 @@ class ChartEditorDialogHandler var dialogContinue:Null