Merge pull request #167 from FunkinCrew/rewrite/feature/snapped-paste

Chart Editor: Snapped Paste and Event Visiblity
This commit is contained in:
Cameron Taylor 2023-10-15 19:39:56 -04:00 committed by GitHub
commit 965363c062
4 changed files with 90 additions and 20 deletions

2
assets

@ -1 +1 @@
Subproject commit f2e37de1ff308eeaf65babad2a4089096c40cedb Subproject commit 8104d43e584a1f25e574438d7b21a7e671358969

View file

@ -21,11 +21,21 @@ class SongDataUtils
* @param notes The notes to modify. * @param notes The notes to modify.
* @param offset The time difference to apply in milliseconds. * @param offset The time difference to apply in milliseconds.
*/ */
public static function offsetSongNoteData(notes:Array<SongNoteData>, offset:Int):Array<SongNoteData> public static function offsetSongNoteData(notes:Array<SongNoteData>, offset:Float):Array<SongNoteData>
{ {
return notes.map(function(note:SongNoteData):SongNoteData { var offsetNote = function(note:SongNoteData):SongNoteData {
return new SongNoteData(note.time + offset, note.data, note.length, note.kind); var time:Float = note.time + offset;
}); var data:Int = note.data;
var length:Float = note.length;
var kind:String = note.kind;
return new SongNoteData(time, data, length, kind);
};
trace(notes);
trace(notes[0]);
var result = [for (i in 0...notes.length) offsetNote(notes[i])];
trace(result);
return result;
} }
/** /**
@ -36,7 +46,7 @@ class SongDataUtils
* @param events The events to modify. * @param events The events to modify.
* @param offset The time difference to apply in milliseconds. * @param offset The time difference to apply in milliseconds.
*/ */
public static function offsetSongEventData(events:Array<SongEventData>, offset:Int):Array<SongEventData> public static function offsetSongEventData(events:Array<SongEventData>, offset:Float):Array<SongEventData>
{ {
return events.map(function(event:SongEventData):SongEventData { return events.map(function(event:SongEventData):SongEventData {
return new SongEventData(event.time + offset, event.event, event.value); return new SongEventData(event.time + offset, event.event, event.value);
@ -152,7 +162,8 @@ class SongDataUtils
*/ */
public static function writeItemsToClipboard(data:SongClipboardItems):Void public static function writeItemsToClipboard(data:SongClipboardItems):Void
{ {
var dataString = SerializerUtil.toJSON(data); var writer = new json2object.JsonWriter<SongClipboardItems>();
var dataString:String = writer.write(data, ' ');
ClipboardUtil.setClipboard(dataString); ClipboardUtil.setClipboard(dataString);
@ -170,19 +181,24 @@ class SongDataUtils
trace('Read ${notesString.length} characters from clipboard.'); trace('Read ${notesString.length} characters from clipboard.');
var data:SongClipboardItems = notesString.parseJSON(); var parser = new json2object.JsonParser<SongClipboardItems>();
parser.fromJson(notesString, 'clipboard');
if (data == null) if (parser.errors.length > 0)
{ {
trace('Failed to parse notes from clipboard.'); trace('[SongDataUtils] Error parsing note JSON data from clipboard.');
for (error in parser.errors)
DataError.printError(error);
return { return {
valid: false,
notes: [], notes: [],
events: [] events: []
}; };
} }
else else
{ {
var data:SongClipboardItems = parser.value;
trace('Parsed ' + data.notes.length + ' notes and ' + data.events.length + ' from clipboard.'); trace('Parsed ' + data.notes.length + ' notes and ' + data.events.length + ' from clipboard.');
data.valid = true;
return data; return data;
} }
} }
@ -230,6 +246,7 @@ class SongDataUtils
typedef SongClipboardItems = typedef SongClipboardItems =
{ {
?valid:Bool,
notes:Array<SongNoteData>, notes:Array<SongNoteData>,
events:Array<SongEventData> events:Array<SongEventData>
} }

View file

@ -1,5 +1,7 @@
package funkin.ui.debug.charting; package funkin.ui.debug.charting;
import haxe.ui.notifications.NotificationType;
import haxe.ui.notifications.NotificationManager;
import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongEventData;
import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongData.SongNoteData;
import funkin.data.song.SongDataUtils; import funkin.data.song.SongDataUtils;
@ -760,6 +762,22 @@ class PasteItemsCommand implements ChartEditorCommand
{ {
var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard(); var currentClipboard:SongClipboardItems = SongDataUtils.readItemsFromClipboard();
if (currentClipboard.valid != true)
{
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Failed to Paste',
body: 'Could not parse clipboard contents.',
type: NotificationType.Error,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
#end
return;
}
trace(currentClipboard.notes);
addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp)); addedNotes = SongDataUtils.offsetSongNoteData(currentClipboard.notes, Std.int(targetTimestamp));
addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp)); addedEvents = SongDataUtils.offsetSongEventData(currentClipboard.events, Std.int(targetTimestamp));
@ -773,6 +791,16 @@ class PasteItemsCommand implements ChartEditorCommand
state.notePreviewDirty = true; state.notePreviewDirty = true;
state.sortChartData(); state.sortChartData();
#if !mac
NotificationManager.instance.addNotification(
{
title: 'Paste Successful',
body: 'Successfully pasted clipboard contents.',
type: NotificationType.Success,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
#end
} }
public function undo(state:ChartEditorState):Void public function undo(state:ChartEditorState):Void

View file

@ -1547,11 +1547,11 @@ class ChartEditorState extends HaxeUIState
renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y); renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y);
add(renderedEvents); add(renderedEvents);
renderedNotes.zIndex = 25; renderedEvents.zIndex = 25;
renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y); renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y);
add(renderedSelectionSquares); add(renderedSelectionSquares);
renderedNotes.zIndex = 26; renderedSelectionSquares.zIndex = 26;
} }
function buildAdditionalUI():Void function buildAdditionalUI():Void
@ -1662,7 +1662,18 @@ class ChartEditorState extends HaxeUIState
addUIClickListener('menubarItemCut', _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection))); addUIClickListener('menubarItemCut', _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection)));
addUIClickListener('menubarItemPaste', _ -> performCommand(new PasteItemsCommand(scrollPositionInMs + playheadPositionInMs))); addUIClickListener('menubarItemPaste', _ -> {
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
var targetStep:Float = Conductor.getTimeInSteps(targetMs);
var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio;
var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep);
performCommand(new PasteItemsCommand(targetSnappedMs));
});
addUIClickListener('menubarItemPasteUnsnapped', _ -> {
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
performCommand(new PasteItemsCommand(targetMs));
});
addUIClickListener('menubarItemDelete', function(_) { addUIClickListener('menubarItemDelete', function(_) {
if (currentNoteSelection.length > 0 && currentEventSelection.length > 0) if (currentNoteSelection.length > 0 && currentEventSelection.length > 0)
@ -2335,7 +2346,6 @@ class ChartEditorState extends HaxeUIState
// Scroll up. // Scroll up.
var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.screenY; var diff:Float = MENU_BAR_HEIGHT - FlxG.mouse.screenY;
scrollPositionInPixels -= diff * 0.5; // Too fast! scrollPositionInPixels -= diff * 0.5; // Too fast!
trace('Scroll up: ' + diff);
moveSongToScrollPosition(); moveSongToScrollPosition();
} }
else if (FlxG.mouse.screenY > (playbarHeadLayout?.y ?? 0.0)) else if (FlxG.mouse.screenY > (playbarHeadLayout?.y ?? 0.0))
@ -2343,7 +2353,6 @@ class ChartEditorState extends HaxeUIState
// Scroll down. // Scroll down.
var diff:Float = FlxG.mouse.screenY - (playbarHeadLayout?.y ?? 0.0); var diff:Float = FlxG.mouse.screenY - (playbarHeadLayout?.y ?? 0.0);
scrollPositionInPixels += diff * 0.5; // Too fast! scrollPositionInPixels += diff * 0.5; // Too fast!
trace('Scroll down: ' + diff);
moveSongToScrollPosition(); moveSongToScrollPosition();
} }
@ -2968,8 +2977,8 @@ class ChartEditorState extends HaxeUIState
// Set the position and size (because we might be recycling one with bad values). // Set the position and size (because we might be recycling one with bad values).
selectionSquare.x = noteSprite.x; selectionSquare.x = noteSprite.x;
selectionSquare.y = noteSprite.y; selectionSquare.y = noteSprite.y;
selectionSquare.width = noteSprite.width; selectionSquare.width = GRID_SIZE;
selectionSquare.height = noteSprite.height; selectionSquare.height = GRID_SIZE;
} }
} }
@ -3000,6 +3009,8 @@ class ChartEditorState extends HaxeUIState
FlxG.watch.addQuick("tapNotesRendered", renderedNotes.members.length); FlxG.watch.addQuick("tapNotesRendered", renderedNotes.members.length);
FlxG.watch.addQuick("holdNotesRendered", renderedHoldNotes.members.length); FlxG.watch.addQuick("holdNotesRendered", renderedHoldNotes.members.length);
FlxG.watch.addQuick("eventsRendered", renderedEvents.members.length); FlxG.watch.addQuick("eventsRendered", renderedEvents.members.length);
FlxG.watch.addQuick("notesSelected", currentNoteSelection.length);
FlxG.watch.addQuick("eventsSelected", currentEventSelection.length);
} }
/** /**
@ -3029,6 +3040,8 @@ class ChartEditorState extends HaxeUIState
if (selectionSquareBitmap == null) if (selectionSquareBitmap == null)
throw "ERROR: Tried to build selection square, but selectionSquareBitmap is null! Check ChartEditorThemeHandler.updateSelectionSquare()"; throw "ERROR: Tried to build selection square, but selectionSquareBitmap is null! Check ChartEditorThemeHandler.updateSelectionSquare()";
FlxG.bitmapLog.add(selectionSquareBitmap, "selectionSquareBitmap");
return new FlxSprite().loadGraphic(selectionSquareBitmap); return new FlxSprite().loadGraphic(selectionSquareBitmap);
} }
@ -3148,8 +3161,20 @@ class ChartEditorState extends HaxeUIState
// CTRL + V = Paste // CTRL + V = Paste
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.V) if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.V)
{ {
// Paste notes from clipboard, at the playhead. // CTRL + SHIFT + V = Paste Unsnapped.
performCommand(new PasteItemsCommand(scrollPositionInMs + playheadPositionInMs)); var targetMs:Float = if (FlxG.keys.pressed.SHIFT)
{
scrollPositionInMs + playheadPositionInMs;
}
else
{
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
var targetStep:Float = Conductor.getTimeInSteps(targetMs);
var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio;
var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep);
targetSnappedMs;
}
performCommand(new PasteItemsCommand(targetMs));
} }
// DELETE = Delete // DELETE = Delete