1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-08-20 07:25:59 +00:00

Redo logic around note selection, add new buttons for selecting individual sides.

This commit is contained in:
EliteMasterEric 2024-01-04 08:20:34 -05:00
parent e1b92e8829
commit 4a0a330eb4
29 changed files with 600 additions and 156 deletions

2
assets

@ -1 +1 @@
Subproject commit 9ecc4d26fe6b26f31782cccfcd7331bd8a318ce1
Subproject commit 198c3ab87e401e595be50814af0f667eb1be25e7

View file

@ -273,7 +273,7 @@ class SongDataUtils
}
/**
* Filter a list of notes to only include notes whose data is within the given range.
* Filter a list of notes to only include notes whose data is within the given range, inclusive.
*/
public static function getNotesInDataRange(notes:Array<SongNoteData>, start:Int, end:Int):Array<SongNoteData>
{

View file

@ -61,6 +61,7 @@ import funkin.ui.debug.charting.commands.AddNotesCommand;
import funkin.ui.debug.charting.commands.ChartEditorCommand;
import funkin.ui.debug.charting.commands.ChartEditorCommand;
import funkin.ui.debug.charting.commands.CutItemsCommand;
import funkin.ui.debug.charting.commands.CopyItemsCommand;
import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
import funkin.ui.debug.charting.commands.DeselectItemsCommand;
import funkin.ui.debug.charting.commands.ExtendNoteLengthCommand;
@ -103,6 +104,7 @@ import haxe.ui.components.Slider;
import haxe.ui.components.TextField;
import haxe.ui.containers.dialogs.CollapsibleDialog;
import haxe.ui.containers.Frame;
import haxe.ui.containers.Box;
import haxe.ui.containers.menus.Menu;
import haxe.ui.containers.menus.MenuBar;
import haxe.ui.containers.menus.MenuItem;
@ -190,10 +192,40 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
public static final PLAYBAR_HEIGHT:Int = 48;
/**
* The height of the note selection buttons above the grid.
*/
public static final NOTE_SELECT_BUTTON_HEIGHT:Int = 24;
/**
* The amount of padding between the menu bar and the chart grid when fully scrolled up.
*/
public static final GRID_TOP_PAD:Int = 8;
public static final GRID_TOP_PAD:Int = NOTE_SELECT_BUTTON_HEIGHT + 12;
/**
* The initial vertical position of the chart grid.
*/
public static final GRID_INITIAL_Y_POS:Int = MENU_BAR_HEIGHT + GRID_TOP_PAD;
/**
* The X position of the note preview area.
*/
public static final NOTE_PREVIEW_X_POS:Int = 350;
/**
* The Y position of the note preview area.
*/
public static final NOTE_PREVIEW_Y_POS:Int = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 4;
/**
* The X position of the note grid.
*/
public static var GRID_X_POS(get, never):Float;
static function get_GRID_X_POS():Float
{
return FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE;
}
// Colors
// Background color tint.
@ -338,21 +370,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
if (isViewDownscroll)
{
gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD);
gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS);
gridPlayheadScrollArea.y = gridTiledSprite.y;
}
else
{
gridTiledSprite.y = -scrollPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD);
gridTiledSprite.y = -scrollPositionInPixels + (GRID_INITIAL_Y_POS);
gridPlayheadScrollArea.y = gridTiledSprite.y;
if (audioVisGroup != null && audioVisGroup.playerVis != null)
{
audioVisGroup.playerVis.y = Math.max(gridTiledSprite.y, MENU_BAR_HEIGHT);
audioVisGroup.playerVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS);
}
if (audioVisGroup != null && audioVisGroup.opponentVis != null)
{
audioVisGroup.opponentVis.y = Math.max(gridTiledSprite.y, MENU_BAR_HEIGHT);
audioVisGroup.opponentVis.y = Math.max(gridTiledSprite.y, GRID_INITIAL_Y_POS);
}
}
}
@ -422,7 +454,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
this.playheadPositionInPixels = value;
// Move the playhead sprite to the correct position.
gridPlayhead.y = this.playheadPositionInPixels + (MENU_BAR_HEIGHT + GRID_TOP_PAD);
gridPlayhead.y = this.playheadPositionInPixels + GRID_INITIAL_Y_POS;
return this.playheadPositionInPixels;
}
@ -1602,6 +1634,24 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/
var playbarEnd:Button;
/**
* The button above the grid that selects all notes on the opponent's side.
* Constructed manually and added to the layout so we can control its position.
*/
var buttonSelectOpponent:Button;
/**
* The button above the grid that selects all notes on the player's side.
* Constructed manually and added to the layout so we can control its position.
*/
var buttonSelectPlayer:Button;
/**
* The button above the grid that selects all song events.
* Constructed manually and added to the layout so we can control its position.
*/
var buttonSelectEvent:Button;
/**
* RENDER OBJECTS
*/
@ -2094,8 +2144,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (gridBitmap == null) throw 'ERROR: Tried to build grid, but gridBitmap is null! Check ChartEditorThemeHandler.updateTheme().';
gridTiledSprite = new FlxTiledSprite(gridBitmap, gridBitmap.width, 1000, false, true);
gridTiledSprite.x = FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; // Center the grid.
gridTiledSprite.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; // Push down to account for the menu bar.
gridTiledSprite.x = GRID_X_POS; // Center the grid.
gridTiledSprite.y = GRID_INITIAL_Y_POS; // Push down to account for the menu bar.
add(gridTiledSprite);
gridTiledSprite.zIndex = 10;
@ -2127,8 +2177,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(gridPlayheadScrollArea);
gridPlayheadScrollArea.setGraphicSize(PLAYHEAD_SCROLL_AREA_WIDTH, 3000);
gridPlayheadScrollArea.updateHitbox();
gridPlayheadScrollArea.x = gridTiledSprite.x - PLAYHEAD_SCROLL_AREA_WIDTH;
gridPlayheadScrollArea.y = MENU_BAR_HEIGHT + GRID_TOP_PAD;
gridPlayheadScrollArea.x = GRID_X_POS - PLAYHEAD_SCROLL_AREA_WIDTH;
gridPlayheadScrollArea.y = GRID_INITIAL_Y_POS;
gridPlayheadScrollArea.zIndex = 25;
// The playhead that show the current position in the song.
@ -2136,8 +2186,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
gridPlayhead.zIndex = 30;
var playheadWidth:Int = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH * 2);
var playheadBaseYPos:Float = MENU_BAR_HEIGHT + GRID_TOP_PAD;
gridPlayhead.setPosition(gridTiledSprite.x, playheadBaseYPos);
var playheadBaseYPos:Float = GRID_INITIAL_Y_POS;
gridPlayhead.setPosition(GRID_X_POS, playheadBaseYPos);
var playheadSprite:FlxSprite = new FlxSprite().makeGraphic(playheadWidth, PLAYHEAD_HEIGHT, PLAYHEAD_COLOR);
playheadSprite.x = -PLAYHEAD_SCROLL_AREA_WIDTH;
playheadSprite.y = 0;
@ -2168,10 +2218,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function buildNotePreview():Void
{
var height:Int = FlxG.height - MENU_BAR_HEIGHT - GRID_TOP_PAD - PLAYBAR_HEIGHT - GRID_TOP_PAD - GRID_TOP_PAD;
notePreview = new ChartEditorNotePreview(height);
notePreview.x = 350;
notePreview.y = MENU_BAR_HEIGHT + GRID_TOP_PAD;
var playbarHeightWithPad = PLAYBAR_HEIGHT + 10;
var notePreviewHeight:Int = FlxG.height - NOTE_PREVIEW_Y_POS - playbarHeightWithPad;
notePreview = new ChartEditorNotePreview(notePreviewHeight);
notePreview.x = NOTE_PREVIEW_X_POS;
notePreview.y = NOTE_PREVIEW_Y_POS;
add(notePreview);
if (notePreviewViewport == null) throw 'ERROR: Tried to build note preview, but notePreviewViewport is null! Check ChartEditorThemeHandler.updateTheme().';
@ -2355,6 +2406,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
add(playbarHeadLayout);
// Little text that shows up when you copy something.
txtCopyNotif = new FlxText(0, 0, 0, '', 24);
txtCopyNotif.setBorderStyle(OUTLINE, 0xFF074809, 1);
txtCopyNotif.color = 0xFF52FF77;
@ -2379,6 +2431,77 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
this.openCharacterDropdown(CharacterType.BF, true);
}
});
buttonSelectOpponent = new Button();
buttonSelectOpponent.allowFocus = false;
buttonSelectOpponent.text = "Opponent"; // Default text.
buttonSelectOpponent.x = GRID_X_POS;
buttonSelectOpponent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 8;
buttonSelectOpponent.width = GRID_SIZE * 4;
buttonSelectOpponent.height = NOTE_SELECT_BUTTON_HEIGHT;
buttonSelectOpponent.tooltip = "Click to set selection to all notes on this side.\nShift-click to add all notes on this side to selection.";
buttonSelectOpponent.zIndex = 110;
add(buttonSelectOpponent);
buttonSelectOpponent.onClick = (_) -> {
var notesToSelect:Array<SongNoteData> = currentSongChartNoteData;
notesToSelect = SongDataUtils.getNotesInDataRange(notesToSelect, STRUMLINE_SIZE, STRUMLINE_SIZE * 2 - 1);
if (FlxG.keys.pressed.SHIFT)
{
performCommand(new SelectItemsCommand(notesToSelect, []));
}
else
{
performCommand(new SetItemSelectionCommand(notesToSelect, []));
}
}
buttonSelectPlayer = new Button();
buttonSelectPlayer.allowFocus = false;
buttonSelectPlayer.text = "Player"; // Default text.
buttonSelectPlayer.x = buttonSelectOpponent.x + buttonSelectOpponent.width;
buttonSelectPlayer.y = buttonSelectOpponent.y;
buttonSelectPlayer.width = GRID_SIZE * 4;
buttonSelectPlayer.height = NOTE_SELECT_BUTTON_HEIGHT;
buttonSelectPlayer.tooltip = "Click to set selection to all notes on this side.\nShift-click to add all notes on this side to selection.";
buttonSelectPlayer.zIndex = 110;
add(buttonSelectPlayer);
buttonSelectPlayer.onClick = (_) -> {
var notesToSelect:Array<SongNoteData> = currentSongChartNoteData;
notesToSelect = SongDataUtils.getNotesInDataRange(notesToSelect, 0, STRUMLINE_SIZE - 1);
if (FlxG.keys.pressed.SHIFT)
{
performCommand(new SelectItemsCommand(notesToSelect, []));
}
else
{
performCommand(new SetItemSelectionCommand(notesToSelect, []));
}
}
buttonSelectEvent = new Button();
buttonSelectEvent.allowFocus = false;
buttonSelectEvent.icon = Paths.image('ui/chart-editor/events/Default');
buttonSelectEvent.iconPosition = "top";
buttonSelectEvent.x = buttonSelectPlayer.x + buttonSelectPlayer.width;
buttonSelectEvent.y = buttonSelectPlayer.y;
buttonSelectEvent.width = GRID_SIZE;
buttonSelectEvent.height = NOTE_SELECT_BUTTON_HEIGHT;
buttonSelectEvent.tooltip = "Click to set selection to all events.\nShift-click to add all events to selection.";
buttonSelectEvent.zIndex = 110;
add(buttonSelectEvent);
buttonSelectEvent.onClick = (_) -> {
if (FlxG.keys.pressed.SHIFT)
{
performCommand(new SelectItemsCommand([], currentSongChartEventData));
}
else
{
performCommand(new SetItemSelectionCommand([], currentSongChartEventData));
}
}
}
/**
@ -2467,23 +2590,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
menubarItemUndo.onClick = _ -> undoLastCommand();
menubarItemRedo.onClick = _ -> redoLastCommand();
menubarItemCopy.onClick = function(_) {
// Doesn't use a command because it's not undoable.
// Calculate a single time offset for all the notes and events.
var timeOffset:Null<Int> = currentNoteSelection.length > 0 ? Std.int(currentNoteSelection[0].time) : null;
if (currentEventSelection.length > 0)
{
if (timeOffset == null || currentEventSelection[0].time < timeOffset)
{
timeOffset = Std.int(currentEventSelection[0].time);
}
}
SongDataUtils.writeItemsToClipboard(
{
notes: SongDataUtils.buildNoteClipboard(currentNoteSelection, timeOffset),
events: SongDataUtils.buildEventClipboard(currentEventSelection, timeOffset),
});
};
menubarItemCut.onClick = _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection));
@ -2521,11 +2627,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
menubarItemFlipNotes.onClick = _ -> performCommand(new FlipNotesCommand(currentNoteSelection));
menubarItemSelectAll.onClick = _ -> performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection));
menubarItemSelectAllNotes.onClick = _ -> performCommand(new SelectAllItemsCommand(true, false));
menubarItemSelectInverse.onClick = _ -> performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection));
menubarItemSelectAllEvents.onClick = _ -> performCommand(new SelectAllItemsCommand(false, true));
menubarItemSelectNone.onClick = _ -> performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection));
menubarItemSelectInverse.onClick = _ -> performCommand(new InvertSelectedItemsCommand());
menubarItemSelectNone.onClick = _ -> performCommand(new DeselectAllItemsCommand());
menubarItemPlaytestFull.onClick = _ -> testSongInPlayState(false);
menubarItemPlaytestMinimal.onClick = _ -> testSongInPlayState(true);
@ -2989,7 +3097,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
if (holdNoteSprite == null || holdNoteSprite.noteData == null || !holdNoteSprite.exists || !holdNoteSprite.visible) continue;
if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD))
if (!holdNoteSprite.isHoldNoteVisible(FlxG.height - PLAYBAR_HEIGHT, MENU_BAR_HEIGHT))
{
holdNoteSprite.kill();
}
@ -3025,7 +3133,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// Resolve an issue where dragging an event too far would cause it to be hidden.
var isSelectedAndDragged = currentEventSelection.fastContains(eventSprite.eventData) && (dragTargetCurrentStep != 0);
if ((eventSprite.isEventVisible(FlxG.height - MENU_BAR_HEIGHT, GRID_TOP_PAD)
if ((eventSprite.isEventVisible(FlxG.height - PLAYBAR_HEIGHT, MENU_BAR_HEIGHT)
&& currentSongChartEventData.fastContains(eventSprite.eventData))
|| isSelectedAndDragged)
{
@ -3569,7 +3677,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
// Clicked on the playhead scroll area.
// Move the playhead to the cursor position.
this.playheadPositionInPixels = FlxG.mouse.screenY - MENU_BAR_HEIGHT - GRID_TOP_PAD;
this.playheadPositionInPixels = FlxG.mouse.screenY - (GRID_INITIAL_Y_POS);
moveSongToScrollPosition();
// Cursor should be a grabby hand.
@ -3662,7 +3770,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else
{
// Set the selection.
performCommand(new SetItemSelectionCommand(notesToSelect, eventsToSelect, currentNoteSelection, currentEventSelection));
performCommand(new SetItemSelectionCommand(notesToSelect, eventsToSelect));
}
}
else
@ -3675,7 +3783,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0);
if (shouldDeselect)
{
performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection));
performCommand(new DeselectAllItemsCommand());
}
}
}
@ -3780,12 +3888,12 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (highlightedNote != null && highlightedNote.noteData != null)
{
// Click a note to select it.
performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [], currentNoteSelection, currentEventSelection));
performCommand(new SetItemSelectionCommand([highlightedNote.noteData], []));
}
else if (highlightedEvent != null && highlightedEvent.eventData != null)
{
// Click an event to select it.
performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData], currentNoteSelection, currentEventSelection));
performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData]));
}
else
{
@ -3793,7 +3901,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0);
if (shouldDeselect)
{
performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection));
performCommand(new DeselectAllItemsCommand());
}
}
}
@ -3808,7 +3916,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var shouldDeselect:Bool = !wasCursorOverHaxeUI && (currentNoteSelection.length > 0 || currentEventSelection.length > 0);
if (shouldDeselect)
{
performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection));
performCommand(new DeselectAllItemsCommand());
}
}
}
@ -4049,7 +4157,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else
{
// If you click an unselected note, and aren't holding Control, deselect everything else.
performCommand(new SetItemSelectionCommand([highlightedNote.noteData], [], currentNoteSelection, currentEventSelection));
performCommand(new SetItemSelectionCommand([highlightedNote.noteData], []));
}
}
else if (highlightedEvent != null && highlightedEvent.eventData != null)
@ -4062,7 +4170,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else
{
// If you click an unselected event, and aren't holding Control, deselect everything else.
performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData], currentNoteSelection, currentEventSelection));
performCommand(new SetItemSelectionCommand([], [highlightedEvent.eventData]));
}
}
else
@ -4324,7 +4432,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (currentSongMetadata.playData.characters.player != charPlayer.charId)
{
if (healthIconBF != null) healthIconBF.characterId = currentSongMetadata.playData.characters.player;
if (healthIconBF != null)
{
healthIconBF.characterId = currentSongMetadata.playData.characters.player;
}
charPlayer.loadCharacter(currentSongMetadata.playData.characters.player);
charPlayer.characterType = CharacterType.BF;
@ -4360,7 +4471,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (currentSongMetadata.playData.characters.opponent != charPlayer.charId)
{
if (healthIconDad != null) healthIconDad.characterId = currentSongMetadata.playData.characters.opponent;
if (healthIconDad != null)
{
healthIconDad.characterId = currentSongMetadata.playData.characters.opponent;
}
charPlayer.loadCharacter(currentSongMetadata.playData.characters.opponent);
charPlayer.characterType = CharacterType.DAD;
@ -4378,6 +4492,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
}
}
function handleSelectionButtons():Void
{
// Make sure buttons are never nudged out of the correct spot.
// TODO: Why do these even move in the first place? The camera never moves, LOL.
buttonSelectOpponent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 2;
buttonSelectPlayer.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 2;
buttonSelectEvent.y = GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT - 2;
}
/**
* Handles display elements for the playbar at the bottom.
*/
@ -4486,11 +4609,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
healthIconBF.size *= 0.5; // Make the icon smaller in Chart Editor.
healthIconBF.flipX = !healthIconBF.flipX; // BF faces the other way.
}
if (buttonSelectPlayer != null)
{
buttonSelectPlayer.text = charDataBF?.name ?? 'Player';
}
if (healthIconDad != null)
{
healthIconDad.configure(charDataDad?.healthIcon);
healthIconDad.size *= 0.5; // Make the icon smaller in Chart Editor.
}
if (buttonSelectOpponent != null)
{
buttonSelectOpponent.text = charDataDad?.name ?? 'Opponent';
}
healthIconsDirty = false;
}
@ -4498,15 +4629,19 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (healthIconBF != null)
{
// Base X position to the right of the grid.
healthIconBF.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x + gridTiledSprite.width + 45 - (healthIconBF.width / 2));
healthIconBF.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconBF.height / 2));
var xOffset = 45 - (healthIconBF.width / 2);
healthIconBF.x = (gridTiledSprite == null) ? (0) : (GRID_X_POS + gridTiledSprite.width + xOffset);
var yOffset = 30 - (healthIconBF.height / 2);
healthIconBF.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT) + yOffset;
}
// Visibly center the Dad health icon.
if (healthIconDad != null)
{
healthIconDad.x = (gridTiledSprite == null) ? (0) : (gridTiledSprite.x - 45 - (healthIconDad.width / 2));
healthIconDad.y = (gridTiledSprite == null) ? (0) : (MENU_BAR_HEIGHT + GRID_TOP_PAD + 30 - (healthIconDad.height / 2));
var xOffset = 45 + (healthIconDad.width / 2);
healthIconDad.x = (gridTiledSprite == null) ? (0) : (GRID_X_POS - xOffset);
var yOffset = 30 - (healthIconDad.height / 2);
healthIconDad.y = (gridTiledSprite == null) ? (0) : (GRID_INITIAL_Y_POS - NOTE_SELECT_BUTTON_HEIGHT) + yOffset;
}
}
@ -4586,54 +4721,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
// CTRL + C = Copy
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.C)
{
if (currentNoteSelection.length > 0)
{
txtCopyNotif.visible = true;
txtCopyNotif.text = "Copied " + currentNoteSelection.length + " notes to clipboard";
txtCopyNotif.x = FlxG.mouse.x - (txtCopyNotif.width / 2);
txtCopyNotif.y = FlxG.mouse.y - 16;
FlxTween.tween(txtCopyNotif, {y: txtCopyNotif.y - 32}, 0.5,
{
type: FlxTween.ONESHOT,
ease: FlxEase.quadOut,
onComplete: function(_) {
txtCopyNotif.visible = false;
}
});
for (note in renderedNotes.members)
{
if (isNoteSelected(note.noteData))
{
FlxTween.globalManager.cancelTweensOf(note);
FlxTween.globalManager.cancelTweensOf(note.scale);
note.playNoteAnimation();
var prevX:Float = note.scale.x;
var prevY:Float = note.scale.y;
note.scale.x *= 1.2;
note.scale.y *= 1.2;
note.angle = FlxG.random.bool() ? -10 : 10;
FlxTween.tween(note, {"angle": 0}, 0.8, {ease: FlxEase.elasticOut});
FlxTween.tween(note.scale, {"y": prevX, "x": prevY}, 0.7,
{
ease: FlxEase.elasticOut,
onComplete: function(_) {
note.playNoteAnimation();
}
});
}
}
}
// We don't need a command for this since we can't undo it.
SongDataUtils.writeItemsToClipboard(
{
notes: SongDataUtils.buildNoteClipboard(currentNoteSelection),
events: SongDataUtils.buildEventClipboard(currentEventSelection),
});
performCommand(new CopyItemsCommand(currentNoteSelection, currentEventSelection));
}
// CTRL + X = Cut
@ -4694,25 +4782,50 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
performCommand(new FlipNotesCommand(currentNoteSelection));
}
// CTRL + A = Select All
// CTRL + A = Select All Notes
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.A)
{
// Select all items.
performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection));
if (FlxG.keys.pressed.ALT)
{
if (FlxG.keys.pressed.SHIFT)
{
// CTRL + ALT + SHIFT + A = Append All Events to Selection
performCommand(new SelectItemsCommand([], currentSongChartEventData));
}
else
{
// CTRL + ALT + A = Set Selection to All Events
performCommand(new SelectAllItemsCommand(false, true));
}
}
else
{
if (FlxG.keys.pressed.SHIFT)
{
// CTRL + SHIFT + A = Append All Notes to Selection
performCommand(new SelectItemsCommand(currentSongChartNoteData, []));
}
else
{
// CTRL + A = Set Selection to All Notes
performCommand(new SelectAllItemsCommand(true, false));
}
}
}
// CTRL + I = Select Inverse
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.I)
{
// Select unselected items and deselect selected items.
performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection));
performCommand(new InvertSelectedItemsCommand());
}
// CTRL + D = Select None
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.D)
{
// Deselect all items.
performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection));
performCommand(new DeselectAllItemsCommand());
}
}
@ -4878,13 +4991,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
* Perform (or redo) a command, then add it to the undo stack.
*
* @param command The command to perform.
* @param purgeRedoStack If true, the redo stack will be cleared.
* @param purgeRedoStack If `true`, the redo stack will be cleared after performing the command.
*/
function performCommand(command:ChartEditorCommand, purgeRedoStack:Bool = true):Void
{
command.execute(this);
undoHistory.push(command);
commandHistoryDirty = true;
if (command.shouldAddToHistory(this))
{
undoHistory.push(command);
commandHistoryDirty = true;
}
if (purgeRedoStack) redoHistory = [];
}
@ -4895,6 +5011,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function undoCommand(command:ChartEditorCommand):Void
{
command.undo(this);
// Note, if we are undoing a command, it should already be in the history,
// therefore we don't need to check `shouldAddToHistory(state)`
redoHistory.push(command);
commandHistoryDirty = true;
}
@ -5515,6 +5633,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
@:privateAccess
ChartEditorNoteSprite.noteFrameCollection = null;
// Stop the music.
welcomeMusic.destroy();
audioInstTrack.destroy();
audioVocalTrackGroup.destroy();
}
function applyCanQuickSave():Void

View file

@ -59,6 +59,12 @@ class AddEventsCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (events.length > 0);
}
public function toString():String
{
var len:Int = events.length;

View file

@ -59,6 +59,12 @@ class AddNotesCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (notes.length > 0);
}
public function toString():String
{
if (notes.length == 1)

View file

@ -54,6 +54,12 @@ class ChangeStartingBPMCommand implements ChartEditorCommand
Conductor.mapTimeChanges(state.currentSongMetadata.timeChanges);
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (targetBPM != previousBPM);
}
public function toString():String
{
return 'Change Starting BPM to ${targetBPM}';

View file

@ -6,6 +6,8 @@ package funkin.ui.debug.charting.commands;
*
* To make a functionality compatible with the undo/redo history, create a new class
* that implements ChartEditorCommand, then call `ChartEditorState.performCommand(new Command())`
*
* NOTE: Make the constructor very simple, as it may be called without executing by the command palette.
*/
interface ChartEditorCommand
{
@ -22,6 +24,15 @@ interface ChartEditorCommand
*/
public function undo(state:ChartEditorState):Void;
/**
* Return whether or not this command should be appended to the in the undo/redo history.
* Generally this should be true, it should only be false if the command is minor and non-destructive,
* like copying to the clipboard.
*
* Called after `execute()` is performed.
*/
public function shouldAddToHistory(state:ChartEditorState):Bool;
/**
* Get a short description of the action (for the UI).
* For example, return `Add Left Note` to display `Undo Add Left Note` in the menu.

View file

@ -0,0 +1,144 @@
package funkin.ui.debug.charting.commands;
import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongDataUtils;
import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
/**
* Command that copies a given set of notes and song events to the clipboard,
* without deleting them from the chart editor.
*/
@:nullSafety
@:access(funkin.ui.debug.charting.ChartEditorState)
class CopyItemsCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
var events:Array<SongEventData>;
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
{
this.notes = notes;
this.events = events;
}
public function execute(state:ChartEditorState):Void
{
// Calculate a single time offset for all the notes and events.
var timeOffset:Null<Int> = state.currentNoteSelection.length > 0 ? Std.int(state.currentNoteSelection[0].time) : null;
if (state.currentEventSelection.length > 0)
{
if (timeOffset == null || state.currentEventSelection[0].time < timeOffset)
{
timeOffset = Std.int(state.currentEventSelection[0].time);
}
}
SongDataUtils.writeItemsToClipboard(
{
notes: SongDataUtils.buildNoteClipboard(state.currentNoteSelection, timeOffset),
events: SongDataUtils.buildEventClipboard(state.currentEventSelection, timeOffset),
});
performVisuals(state);
}
function performVisuals(state:ChartEditorState):Void
{
if (state.currentNoteSelection.length > 0)
{
// Display the "Copied Notes" text.
if (state.txtCopyNotif != null)
{
state.txtCopyNotif.visible = true;
state.txtCopyNotif.text = "Copied " + state.currentNoteSelection.length + " notes to clipboard";
state.txtCopyNotif.x = FlxG.mouse.x - (state.txtCopyNotif.width / 2);
state.txtCopyNotif.y = FlxG.mouse.y - 16;
FlxTween.tween(state.txtCopyNotif, {y: state.txtCopyNotif.y - 32}, 0.5,
{
type: FlxTween.ONESHOT,
ease: FlxEase.quadOut,
onComplete: function(_) {
state.txtCopyNotif.visible = false;
}
});
}
// Wiggle the notes.
for (note in state.renderedNotes.members)
{
if (state.isNoteSelected(note.noteData))
{
FlxTween.globalManager.cancelTweensOf(note);
FlxTween.globalManager.cancelTweensOf(note.scale);
note.playNoteAnimation();
var prevX:Float = note.scale.x;
var prevY:Float = note.scale.y;
note.scale.x *= 1.2;
note.scale.y *= 1.2;
note.angle = FlxG.random.bool() ? -10 : 10;
FlxTween.tween(note, {"angle": 0}, 0.8, {ease: FlxEase.elasticOut});
FlxTween.tween(note.scale, {"y": prevX, "x": prevY}, 0.7,
{
ease: FlxEase.elasticOut,
onComplete: function(_) {
note.playNoteAnimation();
}
});
}
}
// Wiggle the events.
for (event in state.renderedEvents.members)
{
if (state.isEventSelected(event.eventData))
{
FlxTween.globalManager.cancelTweensOf(event);
FlxTween.globalManager.cancelTweensOf(event.scale);
event.playAnimation();
var prevX:Float = event.scale.x;
var prevY:Float = event.scale.y;
event.scale.x *= 1.2;
event.scale.y *= 1.2;
event.angle = FlxG.random.bool() ? -10 : 10;
FlxTween.tween(event, {"angle": 0}, 0.8, {ease: FlxEase.elasticOut});
FlxTween.tween(event.scale, {"y": prevX, "x": prevY}, 0.7,
{
ease: FlxEase.elasticOut,
onComplete: function(_) {
event.playAnimation();
}
});
}
}
}
}
public function undo(state:ChartEditorState):Void
{
// This command is not undoable. Do nothing.
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is not undoable. Don't add it to the history.
return false;
}
public function toString():String
{
var len:Int = notes.length + events.length;
if (notes.length == 0) return 'Copy $len Events to Clipboard';
else if (events.length == 0) return 'Copy $len Notes to Clipboard';
else
return 'Copy $len Items to Clipboard';
}
}

View file

@ -56,6 +56,12 @@ class CutItemsCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Always add it to the history.
return (notes.length > 0 || events.length > 0);
}
public function toString():String
{
var len:Int = notes.length + events.length;

View file

@ -10,17 +10,16 @@ import funkin.data.song.SongData.SongEventData;
@:access(funkin.ui.debug.charting.ChartEditorState)
class DeselectAllItemsCommand implements ChartEditorCommand
{
var previousNoteSelection:Array<SongNoteData>;
var previousEventSelection:Array<SongEventData>;
var previousNoteSelection:Array<SongNoteData> = [];
var previousEventSelection:Array<SongEventData> = [];
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
{
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
}
public function new() {}
public function execute(state:ChartEditorState):Void
{
this.previousNoteSelection = state.currentNoteSelection;
this.previousEventSelection = state.currentEventSelection;
state.currentNoteSelection = [];
state.currentEventSelection = [];
@ -35,6 +34,12 @@ class DeselectAllItemsCommand implements ChartEditorCommand
state.noteDisplayDirty = true;
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (previousNoteSelection.length > 0 || previousEventSelection.length > 0);
}
public function toString():String
{
return 'Deselect All Items';

View file

@ -45,16 +45,27 @@ class DeselectItemsCommand implements ChartEditorCommand
state.notePreviewDirty = true;
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (notes.length > 0 || events.length > 0);
}
public function toString():String
{
var noteCount = notes.length + events.length;
var isPlural = (notes.length + events.length) > 1;
var notesOnly = (notes.length > 0 && events.length == 0);
var eventsOnly = (notes.length == 0 && events.length > 0);
if (noteCount == 1)
if (notesOnly)
{
var dir:String = notes[0].getDirectionName();
return 'Deselect $dir Items';
return 'Deselect ${notes.length} ${isPlural ? 'Notes' : 'Note'}';
}
else if (eventsOnly)
{
return 'Deselect ${events.length} ${isPlural ? 'Events' : 'Event'}';
}
return 'Deselect ${noteCount} Items';
return 'Deselect ${notes.length + events.length} Items';
}
}

View file

@ -45,6 +45,12 @@ class ExtendNoteLengthCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (oldLength != newLength);
}
public function toString():String
{
return 'Extend Note Length';

View file

@ -51,6 +51,12 @@ class FlipNotesCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (notes.length > 0);
}
public function toString():String
{
var len:Int = notes.length;

View file

@ -12,19 +12,19 @@ import funkin.data.song.SongDataUtils;
@:access(funkin.ui.debug.charting.ChartEditorState)
class InvertSelectedItemsCommand implements ChartEditorCommand
{
var previousNoteSelection:Array<SongNoteData>;
var previousEventSelection:Array<SongEventData>;
var previousNoteSelection:Array<SongNoteData> = [];
var previousEventSelection:Array<SongEventData> = [];
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
{
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
}
public function new() {}
public function execute(state:ChartEditorState):Void
{
this.previousNoteSelection = state.currentNoteSelection;
this.previousEventSelection = state.currentEventSelection;
state.currentNoteSelection = SongDataUtils.subtractNotes(state.currentSongChartNoteData, previousNoteSelection);
state.currentEventSelection = SongDataUtils.subtractEvents(state.currentSongChartEventData, previousEventSelection);
state.noteDisplayDirty = true;
}
@ -36,6 +36,12 @@ class InvertSelectedItemsCommand implements ChartEditorCommand
state.noteDisplayDirty = true;
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (previousNoteSelection.length > 0 || previousEventSelection.length > 0);
}
public function toString():String
{
return 'Invert Selected Items';

View file

@ -65,6 +65,12 @@ class MoveEventsCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (events.length > 0);
}
public function toString():String
{
var len:Int = events.length;

View file

@ -88,6 +88,12 @@ class MoveItemsCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (notes.length > 0 || events.length > 0);
}
public function toString():String
{
var len:Int = notes.length + events.length;

View file

@ -67,6 +67,12 @@ class MoveNotesCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (notes.length > 0);
}
public function toString():String
{
var len:Int = notes.length;

View file

@ -71,6 +71,12 @@ class PasteItemsCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (addedNotes.length > 0 || addedEvents.length > 0);
}
public function toString():String
{
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();

View file

@ -48,6 +48,12 @@ class RemoveEventsCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (events.length > 0);
}
public function toString():String
{
if (events.length == 1 && events[0] != null)

View file

@ -62,6 +62,12 @@ class RemoveItemsCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (notes.length > 0 || events.length > 0);
}
public function toString():String
{
return 'Remove ${notes.length + events.length} Items';

View file

@ -50,6 +50,12 @@ class RemoveNotesCommand implements ChartEditorCommand
state.sortChartData();
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (notes.length > 0);
}
public function toString():String
{
if (notes.length == 1 && notes[0] != null)

View file

@ -10,19 +10,25 @@ import funkin.data.song.SongData.SongEventData;
@:access(funkin.ui.debug.charting.ChartEditorState)
class SelectAllItemsCommand implements ChartEditorCommand
{
var previousNoteSelection:Array<SongNoteData>;
var previousEventSelection:Array<SongEventData>;
var shouldSelectNotes:Bool;
var shouldSelectEvents:Bool;
public function new(?previousNoteSelection:Array<SongNoteData>, ?previousEventSelection:Array<SongEventData>)
var previousNoteSelection:Array<SongNoteData> = [];
var previousEventSelection:Array<SongEventData> = [];
public function new(shouldSelectNotes:Bool, shouldSelectEvents:Bool)
{
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
this.shouldSelectNotes = shouldSelectNotes;
this.shouldSelectEvents = shouldSelectEvents;
}
public function execute(state:ChartEditorState):Void
{
state.currentNoteSelection = state.currentSongChartNoteData;
state.currentEventSelection = state.currentSongChartEventData;
this.previousNoteSelection = state.currentNoteSelection;
this.previousEventSelection = state.currentEventSelection;
state.currentNoteSelection = shouldSelectNotes ? state.currentSongChartNoteData : [];
state.currentEventSelection = shouldSelectEvents ? state.currentSongChartEventData : [];
state.noteDisplayDirty = true;
}
@ -35,8 +41,29 @@ class SelectAllItemsCommand implements ChartEditorCommand
state.noteDisplayDirty = true;
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (state.currentNoteSelection.length > 0 || state.currentEventSelection.length > 0);
}
public function toString():String
{
return 'Select All Items';
if (shouldSelectNotes && !shouldSelectEvents)
{
return 'Select All Notes';
}
else if (shouldSelectEvents && !shouldSelectNotes)
{
return 'Select All Events';
}
else if (shouldSelectNotes && shouldSelectEvents)
{
return 'Select All Notes and Events';
}
else
{
return 'Select Nothing (Huh?)';
}
}
}

View file

@ -15,10 +15,10 @@ class SelectItemsCommand implements ChartEditorCommand
var notes:Array<SongNoteData>;
var events:Array<SongEventData>;
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
public function new(?notes:Array<SongNoteData>, ?events:Array<SongEventData>)
{
this.notes = notes;
this.events = events;
this.notes = notes ?? [];
this.events = events ?? [];
}
public function execute(state:ChartEditorState):Void
@ -46,6 +46,12 @@ class SelectItemsCommand implements ChartEditorCommand
state.notePreviewDirty = true;
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// This command is undoable. Add to the history if we actually performed an action.
return (notes.length > 0 || events.length > 0);
}
public function toString():String
{
var len:Int = notes.length + events.length;

View file

@ -13,20 +13,20 @@ class SetItemSelectionCommand implements ChartEditorCommand
{
var notes:Array<SongNoteData>;
var events:Array<SongEventData>;
var previousNoteSelection:Array<SongNoteData>;
var previousEventSelection:Array<SongEventData>;
var previousNoteSelection:Array<SongNoteData> = [];
var previousEventSelection:Array<SongEventData> = [];
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>, previousNoteSelection:Array<SongNoteData>,
previousEventSelection:Array<SongEventData>)
public function new(notes:Array<SongNoteData>, events:Array<SongEventData>)
{
this.notes = notes;
this.events = events;
this.previousNoteSelection = previousNoteSelection == null ? [] : previousNoteSelection;
this.previousEventSelection = previousEventSelection == null ? [] : previousEventSelection;
}
public function execute(state:ChartEditorState):Void
{
this.previousNoteSelection = state.currentNoteSelection;
this.previousEventSelection = state.currentEventSelection;
state.currentNoteSelection = notes;
state.currentEventSelection = events;
@ -41,8 +41,14 @@ class SetItemSelectionCommand implements ChartEditorCommand
state.noteDisplayDirty = true;
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// Add to the history if we actually performed an action.
return (state.currentNoteSelection != previousNoteSelection && state.currentEventSelection != previousEventSelection);
}
public function toString():String
{
return 'Select ${notes.length} Items';
return 'Select ${notes.length + events.length} Items';
}
}

View file

@ -38,6 +38,12 @@ class SwitchDifficultyCommand implements ChartEditorCommand
state.notePreviewDirty = true;
}
public function shouldAddToHistory(state:ChartEditorState):Bool
{
// Add to the history if we actually performed an action.
return (prevVariation != newVariation || prevDifficulty != newDifficulty);
}
public function toString():String
{
return 'Switch Difficulty';

View file

@ -119,8 +119,10 @@ class ChartEditorEventSprite extends FlxSprite
return DEFAULT_EVENT;
}
public function playAnimation(name:String):Void
public function playAnimation(?name:String):Void
{
if (name == null) name = eventData?.event ?? DEFAULT_EVENT;
var correctedName = correctAnimationName(name);
this.animation.play(correctedName);
refresh();

View file

@ -70,9 +70,9 @@ class ChartEditorNotePreview extends FlxSprite
* @param event The data for the event.
* @param songLengthInMs The total length of the song in milliseconds.
*/
public function addEvent(event:SongEventData, songLengthInMs:Int):Void
public function addEvent(event:SongEventData, songLengthInMs:Int, ?isSelection:Bool = false):Void
{
drawNote(-1, false, Std.int(event.time), songLengthInMs);
drawNote(-1, false, Std.int(event.time), songLengthInMs, isSelection);
}
/**
@ -114,6 +114,19 @@ class ChartEditorNotePreview extends FlxSprite
}
}
/**
* Add an array of selected events to the preview.
* @param events The data for the events.
* @param songLengthInMs The total length of the song in milliseconds.
*/
public function addSelectedEvents(events:Array<SongEventData>, songLengthInMs:Int):Void
{
for (event in events)
{
addEvent(event, songLengthInMs, true);
}
}
/**
* Draws a note on the preview.
* @param dir Note data.

View file

@ -185,9 +185,10 @@ class ChartEditorAudioHandler
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
state.audioVisGroup.addPlayerVis(vocalTrack);
state.audioVisGroup.playerVis.x = 885;
state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195;
state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16;
state.audioVisGroup.playerVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; // The height of the visualizer, in time.
state.audioVisGroup.playerVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; // The height of the visualizer, in pixels.
state.audioVisGroup.playerVis.detail = 1;
state.audioVisGroup.playerVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS);
state.audioVocalTrackGroup.playerVoicesOffset = state.currentSongOffsets.getVocalOffset(charId);
return true;
@ -195,10 +196,10 @@ class ChartEditorAudioHandler
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
state.audioVisGroup.addOpponentVis(vocalTrack);
state.audioVisGroup.opponentVis.x = 435;
state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195;
state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16;
state.audioVisGroup.opponentVis.realtimeVisLenght = Conductor.getStepTimeInMs(16) * 0.00195; // The height of the visualizer, in time.
state.audioVisGroup.opponentVis.daHeight = (ChartEditorState.GRID_SIZE) * 16; // The height of the visualizer, in pixels.
state.audioVisGroup.opponentVis.detail = 1;
state.audioVisGroup.opponentVis.y = Math.max(state.gridTiledSprite?.y ?? 0.0, ChartEditorState.GRID_INITIAL_Y_POS);
state.audioVocalTrackGroup.opponentVoicesOffset = state.currentSongOffsets.getVocalOffset(charId);

View file

@ -2,6 +2,10 @@ package funkin.ui.debug.charting.handlers;
import funkin.util.PlatformUtil;
/**
* Handles modifying the shortcut text of menu items based on the current platform.
* On MacOS, `Ctrl`, `Alt`, and `Shift` are replaced with `` (or `^`), ``, and ``, respectively.
*/
@:access(funkin.ui.debug.charting.ChartEditorState)
class ChartEditorShortcutHandler
{
@ -18,7 +22,8 @@ class ChartEditorShortcutHandler
state.menubarItemCopy.shortcutText = ctrlOrCmd('C');
state.menubarItemPaste.shortcutText = ctrlOrCmd('V');
state.menubarItemSelectAll.shortcutText = ctrlOrCmd('A');
state.menubarItemSelectAllNotes.shortcutText = ctrlOrCmd('A');
state.menubarItemSelectAllEvents.shortcutText = ctrlOrCmd(alt('A'));
state.menubarItemSelectInverse.shortcutText = ctrlOrCmd('I');
state.menubarItemSelectNone.shortcutText = ctrlOrCmd('D');
state.menubarItemSelectBeforeCursor.shortcutText = shift('Home');