From d78ead2927b0705e098867e82bd6dbe6e2575d97 Mon Sep 17 00:00:00 2001 From: Lasercar <64717068+Lasercar@users.noreply.github.com> Date: Fri, 25 Apr 2025 21:41:28 +1000 Subject: [PATCH] Refactor time change commands, add @AbnormalPoof suggestion --- .../ui/debug/charting/ChartEditorState.hx | 11 ++++ .../commands/AddNewTimeChangeCommand.hx | 37 +++++++++----- ...eCommand.hx => ModifyTimeChangeCommand.hx} | 51 ++++++++++++------- ...eCommand.hx => RemoveTimeChangeCommand.hx} | 31 ++++++++--- .../toolboxes/ChartEditorMetadataToolbox.hx | 47 +++++++++-------- 5 files changed, 116 insertions(+), 61 deletions(-) rename source/funkin/ui/debug/charting/commands/{ModifyCurrentTimeChangeCommand.hx => ModifyTimeChangeCommand.hx} (50%) rename source/funkin/ui/debug/charting/commands/{RemoveCurrentTimeChangeCommand.hx => RemoveTimeChangeCommand.hx} (52%) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 2bf735fbc..f64a4f230 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -49,6 +49,7 @@ import funkin.play.song.Song; import funkin.save.Save; import funkin.ui.debug.charting.commands.AddEventsCommand; import funkin.ui.debug.charting.commands.AddNotesCommand; +import funkin.ui.debug.charting.commands.AddNewTimeChangeCommand; import funkin.ui.debug.charting.commands.ChartEditorCommand; import funkin.ui.debug.charting.commands.CopyItemsCommand; import funkin.ui.debug.charting.commands.CutItemsCommand; @@ -4362,6 +4363,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } } + if (FlxG.mouse.justPressedRight) + { + if (gridPlayhead != null && FlxG.mouse.overlaps(gridPlayhead) && !isCursorOverHaxeUI) + { + var currentTimeChangeIndex = currentSongMetadata.timeChanges.indexOf(Conductor.instance.currentTimeChange); + // Add a new time change at the grid playhead's position. + performCommand(new AddNewTimeChangeCommand(currentTimeChangeIndex, scrollPositionInMs + playheadPositionInMs)); + } + } + if (FlxG.mouse.justPressed) { if (scrollAnchorScreenPos != null) diff --git a/source/funkin/ui/debug/charting/commands/AddNewTimeChangeCommand.hx b/source/funkin/ui/debug/charting/commands/AddNewTimeChangeCommand.hx index 045cde963..6a3356c78 100644 --- a/source/funkin/ui/debug/charting/commands/AddNewTimeChangeCommand.hx +++ b/source/funkin/ui/debug/charting/commands/AddNewTimeChangeCommand.hx @@ -1,40 +1,43 @@ package funkin.ui.debug.charting.commands; import funkin.data.song.SongData.SongTimeChange; +import funkin.ui.debug.charting.toolboxes.ChartEditorMetadataToolbox; /** - * A command which adds a new timechange to the current song's timechanges, after the currently selected timechange. + * A command which adds a new timechange to the current song's timechanges, after the index value given, at the given timestamp. + * Will clamp the target timestamp to a valid value. */ @:nullSafety @:access(funkin.ui.debug.charting.ChartEditorState) class AddNewTimeChangeCommand implements ChartEditorCommand { - var currentTimeChange:Int; + var timeChangeIndex:Int; var previousTimeChanges:Null>; - var currentStep:Float; + var targetTimeStamp:Float; - public function new(currentTimeChange:Int, currentStep:Float) + public function new(timeChangeIndex:Int, targetTimeStamp:Float) { - this.currentTimeChange = currentTimeChange; - this.currentStep = currentStep; + this.timeChangeIndex = timeChangeIndex; + this.targetTimeStamp = thx.Floats.ceilTo(targetTimeStamp, 4); } public function execute(state:ChartEditorState):Void { var timeChanges:Array = state.currentSongMetadata.timeChanges; - previousTimeChanges = timeChanges; + previousTimeChanges = timeChanges.copy(); if (timeChanges == null || timeChanges.length == 0) { timeChanges = [new SongTimeChange(0, 100)]; } else { - timeChanges.insert(currentTimeChange + 1, - new SongTimeChange(timeChanges[currentTimeChange].timeStamp + currentStep, timeChanges[currentTimeChange].bpm, - timeChanges[currentTimeChange].timeSignatureNum, - timeChanges[currentTimeChange].timeSignatureDen)); + // Clamp the target timestamp to a valid value. + targetTimeStamp.clamp((timeChanges[timeChangeIndex - 1]?.timeStamp ?? 0) + 1, (timeChanges[timeChangeIndex + 1]?.timeStamp ?? state.songLengthInMs) - 1); + timeChanges.insert(timeChangeIndex + 1, + new SongTimeChange(targetTimeStamp, timeChanges[timeChangeIndex].bpm, timeChanges[timeChangeIndex].timeSignatureNum, + timeChanges[timeChangeIndex].timeSignatureDen)); } state.currentSongMetadata.timeChanges = timeChanges; @@ -44,8 +47,13 @@ class AddNewTimeChangeCommand implements ChartEditorCommand state.notePreviewViewportBoundsDirty = true; state.scrollPositionInPixels = 0; + var metadataToolbox:ChartEditorMetadataToolbox = cast state.getToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); + Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); + if (metadataToolbox != null) metadataToolbox.refreshTimeChanges(timeChangeIndex + 1); + + state.updateSongTime(); // basically will update the time signature for the editor if necessary. state.updateGridHeight(); } @@ -63,8 +71,13 @@ class AddNewTimeChangeCommand implements ChartEditorCommand state.notePreviewViewportBoundsDirty = true; state.scrollPositionInPixels = 0; + var metadataToolbox:ChartEditorMetadataToolbox = cast state.getToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); + Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); + if (metadataToolbox != null) metadataToolbox.refreshTimeChanges(timeChangeIndex); + + state.updateSongTime(); state.updateGridHeight(); } @@ -75,6 +88,6 @@ class AddNewTimeChangeCommand implements ChartEditorCommand public function toString():String { - return 'Added new TimeChange after TimeChange ${currentTimeChange}'; + return 'Added new TimeChange ${timeChangeIndex + 1} at ${targetTimeStamp}'; } } diff --git a/source/funkin/ui/debug/charting/commands/ModifyCurrentTimeChangeCommand.hx b/source/funkin/ui/debug/charting/commands/ModifyTimeChangeCommand.hx similarity index 50% rename from source/funkin/ui/debug/charting/commands/ModifyCurrentTimeChangeCommand.hx rename to source/funkin/ui/debug/charting/commands/ModifyTimeChangeCommand.hx index 2e2a21cf5..72fbef1ec 100644 --- a/source/funkin/ui/debug/charting/commands/ModifyCurrentTimeChangeCommand.hx +++ b/source/funkin/ui/debug/charting/commands/ModifyTimeChangeCommand.hx @@ -3,36 +3,43 @@ package funkin.ui.debug.charting.commands; import funkin.data.song.SongData.SongTimeChange; /** - * A command which modifies the current time change's bpm and/or timestamp in the song. - * Note that this does not have any protection to prevent time changes from being set to invalid or troublesome values. + * A command which modifies the give time change in the current song's time changes. + * Annoyingly, due to the way haxe works, every value of the time change has to be passed into this. + * Will clamp the target timestamp to a valid value. */ @:nullSafety @:access(funkin.ui.debug.charting.ChartEditorState) -class ModifyCurrentTimeChangeCommand implements ChartEditorCommand +class ModifyTimeChangeCommand implements ChartEditorCommand { - var currentTimeChange:Int; + var timeChangeIndex:Int; var targetBPM:Float; - var previousBPM:Float = 100; var targetTimeStamp:Float; - var previousTimeStamp:Float = 0; + var targetNumerator:Int; + var previousNumerator:Int = 4; + + var targetDenominator:Int; + var previousDenominator:Int = 4; + var previousTimeChanges:Null>; - public function new(currentTimeChange:Int, targetBPM:Float, targetTimeStamp:Float) + public function new(timeChangeIndex:Int, targetTimeStamp:Float, targetBPM:Float, targetNumerator:Int, targetDenominator:Int) { - this.currentTimeChange = currentTimeChange; - this.targetBPM = targetBPM; - this.targetTimeStamp = targetTimeStamp; + this.timeChangeIndex = timeChangeIndex; + this.targetTimeStamp = thx.Floats.ceilTo(targetTimeStamp, 4); + this.targetBPM = thx.Floats.ceilTo(targetBPM, 3); + this.targetNumerator = targetNumerator; + this.targetDenominator = targetDenominator; } public function execute(state:ChartEditorState):Void { var timeChanges:Array = state.currentSongMetadata.timeChanges; - previousTimeChanges = timeChanges; + previousTimeChanges = timeChanges.copy(); if (timeChanges == null || timeChanges.length == 0) { previousBPM = 100; @@ -41,10 +48,16 @@ class ModifyCurrentTimeChangeCommand implements ChartEditorCommand } else { - previousBPM = timeChanges[currentTimeChange].bpm; - previousTimeStamp = timeChanges[currentTimeChange].timeStamp; - timeChanges[currentTimeChange].bpm = targetBPM; - timeChanges[currentTimeChange].timeStamp = targetTimeStamp; + previousBPM = timeChanges[timeChangeIndex].bpm; + previousTimeStamp = timeChanges[timeChangeIndex].timeStamp; + previousNumerator = timeChanges[timeChangeIndex].timeSignatureNum; + previousDenominator = timeChanges[timeChangeIndex].timeSignatureDen; + timeChanges[timeChangeIndex].bpm = targetBPM; + // Clamp the target timestamp to a valid value. + targetTimeStamp.clamp((timeChanges[timeChangeIndex - 1]?.timeStamp ?? 0) + 1, (timeChanges[timeChangeIndex + 1]?.timeStamp ?? state.songLengthInMs) - 1); + timeChanges[timeChangeIndex].timeStamp = targetTimeStamp; + timeChanges[timeChangeIndex].timeSignatureNum = targetNumerator; + timeChanges[timeChangeIndex].timeSignatureDen = targetDenominator; } state.currentSongMetadata.timeChanges = timeChanges; @@ -56,6 +69,7 @@ class ModifyCurrentTimeChangeCommand implements ChartEditorCommand Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); + state.updateSongTime(); state.updateGridHeight(); } @@ -75,6 +89,7 @@ class ModifyCurrentTimeChangeCommand implements ChartEditorCommand Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); + state.updateSongTime(); state.updateGridHeight(); } @@ -86,9 +101,7 @@ class ModifyCurrentTimeChangeCommand implements ChartEditorCommand public function toString():String { - if (targetBPM == previousBPM && targetTimeStamp != previousTimeStamp) return 'Changed TimeChange ${currentTimeChange} BPM to ${targetBPM}'; - else if (targetBPM != previousBPM && targetTimeStamp == previousTimeStamp) return 'Changed TimeChange ${currentTimeChange} timestamp to ${targetTimeStamp}'; - else - return 'Changed TimeChange ${currentTimeChange} BPM to ${targetBPM} & timestamp to ${targetTimeStamp}'; + return + 'TimeChange ${timeChangeIndex}: ${targetTimeStamp} : BPM: ${targetBPM} in ${targetNumerator}/${targetDenominator}'; } } diff --git a/source/funkin/ui/debug/charting/commands/RemoveCurrentTimeChangeCommand.hx b/source/funkin/ui/debug/charting/commands/RemoveTimeChangeCommand.hx similarity index 52% rename from source/funkin/ui/debug/charting/commands/RemoveCurrentTimeChangeCommand.hx rename to source/funkin/ui/debug/charting/commands/RemoveTimeChangeCommand.hx index 2691cf9ba..85aa28010 100644 --- a/source/funkin/ui/debug/charting/commands/RemoveCurrentTimeChangeCommand.hx +++ b/source/funkin/ui/debug/charting/commands/RemoveTimeChangeCommand.hx @@ -1,34 +1,37 @@ package funkin.ui.debug.charting.commands; import funkin.data.song.SongData.SongTimeChange; +import funkin.ui.debug.charting.toolboxes.ChartEditorMetadataToolbox; /** - * A command which removes the current timechange from the song's timechanges. + * A command which removes the given timechange from the current song's timechanges. */ @:nullSafety @:access(funkin.ui.debug.charting.ChartEditorState) -class RemoveCurrentTimeChangeCommand implements ChartEditorCommand +class RemoveTimeChangeCommand implements ChartEditorCommand { - var currentTimeChange:Int; + var timeChangeIndex:Int; var previousTimeChanges:Null>; - public function new(currentTimeChange:Int) + var removedTimeChange:Null>; + + public function new(timeChangeIndex:Int) { - this.currentTimeChange = currentTimeChange; + this.timeChangeIndex = timeChangeIndex; } public function execute(state:ChartEditorState):Void { var timeChanges:Array = state.currentSongMetadata.timeChanges; - previousTimeChanges = timeChanges; + previousTimeChanges = timeChanges.copy(); if (timeChanges == null || timeChanges.length == 0) { timeChanges = [new SongTimeChange(0, 100)]; } else { - timeChanges.splice(currentTimeChange , 1); + removedTimeChange = timeChanges.splice(timeChangeIndex , 1); } state.currentSongMetadata.timeChanges = timeChanges; @@ -37,8 +40,13 @@ class RemoveCurrentTimeChangeCommand implements ChartEditorCommand state.notePreviewDirty = true; state.notePreviewViewportBoundsDirty = true; + var metadataToolbox:ChartEditorMetadataToolbox = cast state.getToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); + Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); + if (metadataToolbox != null) metadataToolbox.refreshTimeChanges(timeChangeIndex - 1); + + state.updateSongTime(); state.updateGridHeight(); } @@ -55,8 +63,13 @@ class RemoveCurrentTimeChangeCommand implements ChartEditorCommand state.notePreviewDirty = true; state.notePreviewViewportBoundsDirty = true; + var metadataToolbox:ChartEditorMetadataToolbox = cast state.getToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); + Conductor.instance.mapTimeChanges(state.currentSongMetadata.timeChanges); + if (metadataToolbox != null) metadataToolbox.refreshTimeChanges(timeChangeIndex); + + state.updateSongTime(); state.updateGridHeight(); } @@ -67,6 +80,8 @@ class RemoveCurrentTimeChangeCommand implements ChartEditorCommand public function toString():String { - return 'Removed TimeChange ${currentTimeChange}'; + if (removedTimeChange != null && removedTimeChange.length > 0) return + 'TimeChange ${timeChangeIndex} : ${removedTimeChange[0].timeStamp} : BPM: ${removedTimeChange[0].bpm} in ${removedTimeChange[0].timeSignatureNum}/${removedTimeChange[0].timeSignatureDen} removed' + else return 'huh?'; } } diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx index 18ab0b7d2..5a9f8fa7e 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx @@ -7,8 +7,8 @@ import funkin.data.stage.StageRegistry; import funkin.data.notestyle.NoteStyleRegistry; import funkin.play.notes.notestyle.NoteStyle; import funkin.ui.debug.charting.commands.AddNewTimeChangeCommand; -import funkin.ui.debug.charting.commands.ModifyCurrentTimeChangeCommand; -import funkin.ui.debug.charting.commands.RemoveCurrentTimeChangeCommand; +import funkin.ui.debug.charting.commands.ModifyTimeChangeCommand; +import funkin.ui.debug.charting.commands.RemoveTimeChangeCommand; import funkin.ui.debug.charting.util.ChartEditorDropdowns; import haxe.ui.components.Button; import haxe.ui.components.DropDown; @@ -194,7 +194,8 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox var currentBPM = currentTimeChange.bpm; if (event.value != currentBPM) { - chartEditorState.performCommand(new ModifyCurrentTimeChangeCommand(inputTimeChange.selectedIndex, event.value, currentTimeChange.timeStamp)); + chartEditorState.performCommand(new ModifyTimeChangeCommand(inputTimeChange.selectedIndex, currentTimeChange.timeStamp, event.value, + currentTimeChange.timeSignatureNum, currentTimeChange.timeSignatureDen)); inputTimeChange.value.text = '${currentTimeChange.timeStamp} : BPM: ${event.value} in ${currentTimeChange.timeSignatureNum}/${currentTimeChange.timeSignatureDen}'; tcDropdownItemRenderer.data = inputTimeChange.value; } @@ -208,10 +209,10 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox var currentTimeStamp = currentTimeChange.timeStamp; if (inputTimeChange.selectedIndex != 0 && event.value != currentTimeStamp) { - chartEditorState.performCommand(new ModifyCurrentTimeChangeCommand(inputTimeChange.selectedIndex, currentTimeChange.bpm, event.value)); + chartEditorState.performCommand(new ModifyTimeChangeCommand(inputTimeChange.selectedIndex, event.value, currentTimeChange.bpm, + currentTimeChange.timeSignatureNum, currentTimeChange.timeSignatureDen)); inputTimeChange.value.text = '${event.value} : BPM: ${currentTimeChange.bpm} in ${currentTimeChange.timeSignatureNum}/${currentTimeChange.timeSignatureDen}'; tcDropdownItemRenderer.data = inputTimeChange.value; - chartEditorState.updateTimeSignature(); } }; @@ -222,10 +223,10 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox var prevNumerator:Int = currentTimeChange.timeSignatureNum; if (numerator == prevNumerator) return; - chartEditorState.currentSongMetadata.timeChanges[inputTimeChange.selectedIndex].timeSignatureNum = numerator; + chartEditorState.performCommand(new ModifyTimeChangeCommand(inputTimeChange.selectedIndex, event.value, currentTimeChange.bpm, numerator, + currentTimeChange.timeSignatureDen)); inputTimeChange.value.text = '${currentTimeChange.timeStamp} : BPM: ${currentTimeChange.bpm} in ${numerator}/${currentTimeChange.timeSignatureDen}'; tcDropdownItemRenderer.data = inputTimeChange.value; - chartEditorState.updateTimeSignature(); } inputTSDen.onChange = function(event:UIEvent) { @@ -235,24 +236,19 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox var prevDenominator:Int = currentTimeChange.timeSignatureDen; if (denominator == prevDenominator) return; - chartEditorState.currentSongMetadata.timeChanges[inputTimeChange.selectedIndex].timeSignatureDen = denominator; + chartEditorState.performCommand(new ModifyTimeChangeCommand(inputTimeChange.selectedIndex, event.value, currentTimeChange.bpm, + currentTimeChange.timeSignatureNum, denominator)); inputTimeChange.value.text = '${currentTimeChange.timeStamp} : BPM: ${currentTimeChange.bpm} in ${currentTimeChange.timeSignatureNum}/${denominator}'; tcDropdownItemRenderer.data = inputTimeChange.value; - chartEditorState.updateTimeSignature(); } addTimeChange.onClick = function(_:UIEvent) { - chartEditorState.performCommand(new AddNewTimeChangeCommand(inputTimeChange.selectedIndex, inputTimeStamp.step)); - var startingTimeChange = ChartEditorDropdowns.populateDropdownWithTimeChanges(inputTimeChange, chartEditorState.currentSongMetadata.timeChanges, - inputTimeChange.selectedIndex + 1); - inputTimeChange.value = startingTimeChange; + var currentTimeChange = chartEditorState.currentSongMetadata.timeChanges[inputTimeChange.selectedIndex]; + chartEditorState.performCommand(new AddNewTimeChangeCommand(inputTimeChange.selectedIndex, currentTimeChange?.timeStamp + inputTimeStamp.step)); } removeTimeChange.onClick = function(_:UIEvent) { - chartEditorState.performCommand(new RemoveCurrentTimeChangeCommand(inputTimeChange.selectedIndex)); - var startingTimeChange = ChartEditorDropdowns.populateDropdownWithTimeChanges(inputTimeChange, chartEditorState.currentSongMetadata.timeChanges, - inputTimeChange.selectedIndex - 1); - inputTimeChange.value = startingTimeChange; + chartEditorState.performCommand(new RemoveTimeChangeCommand(inputTimeChange.selectedIndex)); } inputScrollSpeed.onChange = function(event:UIEvent) { @@ -289,6 +285,17 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox refresh(); } + // Separate function because it needs to be refreshed in some of the time change commands, updating the entire toolbox would be wasteful. + public function refreshTimeChanges(startingTimeChangeIndex:Int = 0):Void + { + // Reset time change dropdown and the associated inputs + var startingTimeChange = ChartEditorDropdowns.populateDropdownWithTimeChanges(inputTimeChange, chartEditorState.currentSongMetadata.timeChanges, + startingTimeChangeIndex); + inputTimeChange.selectedIndex = Std.parseInt(startingTimeChange.id); + inputTimeChange.value = startingTimeChange; + chartEditorState.updateSongTime(); + } + public override function refresh():Void { super.refresh(); @@ -305,11 +312,7 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox frameVariation.text = 'Variation: ${chartEditorState.selectedVariation.toTitleCase()}'; frameDifficulty.text = 'Difficulty: ${chartEditorState.selectedDifficulty.toTitleCase()}'; - // Reset time change dropdown and the associated inputs - var startingTimeChange = ChartEditorDropdowns.populateDropdownWithTimeChanges(inputTimeChange, chartEditorState.currentSongMetadata.timeChanges, 0); - inputTimeChange.selectedIndex = Std.parseInt(startingTimeChange.id); - inputTimeChange.value = startingTimeChange; - trace('Setting time signature to ${chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureNum}/${chartEditorState.currentSongMetadata.timeChanges[0].timeSignatureDen}'); + refreshTimeChanges(); var stageId:String = chartEditorState.currentSongMetadata.playData.stage; var stage:Null = StageRegistry.instance.fetchEntry(stageId);