diff --git a/assets b/assets index 6f17eb051..23f85072c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 6f17eb051e2609d59a591d4e6eb78e37c6e90adb +Subproject commit 23f85072c190373592a30aed137b034715623f28 diff --git a/hmm.json b/hmm.json index 57fbbb555..d461edd24 100644 --- a/hmm.json +++ b/hmm.json @@ -54,14 +54,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "e765a3e0b7a653823e8dec765e04623f27f573f8", + "ref": "5086e59e7551d775ed4d1fb0188e31de22d1312b", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "7a517d561eff49d8123c128bf9f5c1123b84d014", + "ref": "2b9cff727999b53ed292b1675ac1c9089ac77600", "url": "https://github.com/haxeui/haxeui-flixel" }, { diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 13bcd306e..6c00b6f68 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -197,6 +197,13 @@ class InitState extends FlxState FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK]; #end + // + // FLIXEL PLUGINS + // + funkin.util.plugins.EvacuateDebugPlugin.initialize(); + funkin.util.plugins.ReloadAssetsDebugPlugin.initialize(); + funkin.util.plugins.WatchPlugin.initialize(); + // // GAME DATA PARSING // diff --git a/source/funkin/data/event/SongEventData.hx b/source/funkin/data/event/SongEventData.hx index 7a167b031..522b9314e 100644 --- a/source/funkin/data/event/SongEventData.hx +++ b/source/funkin/data/event/SongEventData.hx @@ -240,4 +240,28 @@ typedef SongEventSchemaField = ?defaultValue:Dynamic, } -typedef SongEventSchema = Array; +@:forward +abstract SongEventSchema(SongEventSchemaRaw) +{ + public function new(?fields:Array) + { + this = fields; + } + + public function getByName(name:String):SongEventSchemaField + { + for (field in this) + { + if (field.name == name) return field; + } + + return null; + } + + public function getFirstField():SongEventSchemaField + { + return this[0]; + } +} + +typedef SongEventSchemaRaw = Array; diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 600871e2f..90a5f47c3 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -617,6 +617,23 @@ abstract SongEventData(SongEventDataRaw) from SongEventDataRaw to SongEventDataR this = new SongEventDataRaw(time, event, value); } + public inline function valueAsStruct(?defaultKey:String = "key"):Dynamic + { + if (this.value == null) return {}; + // TODO: How to check if it's a dynamic struct? + if (Std.isOfType(this.value, Int) || Std.isOfType(this.value, String) || Std.isOfType(this.value, Float) || Std.isOfType(this.value, Bool) + || Std.isOfType(this.value, Array)) + { + var result:haxe.DynamicAccess = {}; + result.set(defaultKey, this.value); + return cast result; + } + else + { + return cast this.value; + } + } + public inline function getDynamic(key:String):Null { return this.value == null ? null : Reflect.field(this.value, key); diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index 077e9e495..f207d48c8 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -71,34 +71,11 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler } } - function handleFunctionControls():Void - { - // Emergency exit button. - if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState()); - - // This can now be used in EVERY STATE YAY! - if (FlxG.keys.justPressed.F5) debug_refreshModules(); - } - - function handleQuickWatch():Void - { - // Display Conductor info in the watch window. - FlxG.watch.addQuick("songPosition", Conductor.songPosition); - FlxG.watch.addQuick("songPositionNoOffset", Conductor.songPosition + Conductor.instrumentalOffset); - FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); - FlxG.watch.addQuick("bpm", Conductor.bpm); - FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime); - FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime); - FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); - } - override function update(elapsed:Float) { super.update(elapsed); handleControls(); - handleFunctionControls(); - handleQuickWatch(); dispatchEvent(new UpdateScriptEvent(elapsed)); } @@ -127,16 +104,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler ModuleHandler.callEvent(event); } - function debug_refreshModules() - { - PolymodHandler.forceReloadAssets(); - - this.destroy(); - - // Create a new instance of the current state, so old data is cleared. - FlxG.resetState(); - } - public function stepHit():Bool { var event = new SongTimeScriptEvent(SONG_STEP_HIT, Conductor.currentBeat, Conductor.currentStep); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index bf1b70d55..294c230d5 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -147,7 +147,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Layouts public static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata'); - public static final CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata'); + public static final CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata'); public static final CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:String = Paths.ui('chart-editor/toolbox/playtest-properties'); public static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata'); public static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty'); @@ -489,17 +489,17 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState /** * The note kind to use for notes being placed in the chart. Defaults to `''`. */ - var selectedNoteKind:String = ''; + var noteKindToPlace:String = ''; /** * The event type to use for events being placed in the chart. Defaults to `''`. */ - var selectedEventKind:String = 'FocusCamera'; + var eventKindToPlace:String = 'FocusCamera'; /** * The event data to use for events being placed in the chart. */ - var selectedEventData:DynamicAccess = {}; + var eventDataToPlace:DynamicAccess = {}; /** * The internal index of what note snapping value is in use. @@ -1871,6 +1871,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Setup the onClick listeners for the UI after it's been created. setupUIListeners(); + setupContextMenu(); setupTurboKeyHandlers(); setupAutoSave(); @@ -2444,23 +2445,7 @@ 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 = 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), - }); + copySelection(); }; menubarItemCut.onClick = _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection)); @@ -2626,7 +2611,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemToggleToolboxDifficulty.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value); menubarItemToggleToolboxMetadata.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value); menubarItemToggleToolboxNotes.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value); - menubarItemToggleToolboxEvents.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT, event.value); + menubarItemToggleToolboxEventData.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT, event.value); menubarItemToggleToolboxPlaytestProperties.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT, event.value); menubarItemToggleToolboxPlayerPreview.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT, event.value); menubarItemToggleToolboxOpponentPreview.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT, event.value); @@ -2635,6 +2620,42 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // registerContextMenu(null, Paths.ui('chart-editor/context/test')); } + function setupContextMenu():Void + { + Screen.instance.registerEvent(MouseEvent.RIGHT_MOUSE_UP, function(e:MouseEvent) { + var xPos = e.screenX; + var yPos = e.screenY; + onContextMenu(xPos, yPos); + }); + } + + function onContextMenu(xPos:Float, yPos:Float) + { + trace('User right clicked to open menu at (${xPos}, ${yPos})'); + // this.openDefaultContextMenu(xPos, yPos); + } + + function copySelection():Void + { + // Doesn't use a command because it's not undoable. + + // Calculate a single time offset for all the notes and events. + var timeOffset:Null = 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), + }); + } + /** * Initialize TurboKeyHandlers and add them to the state (so `update()` is called) * We can then probe `keyHandler.activated` to see if the key combo's action should be taken. @@ -2812,6 +2833,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState playMetronomeTick(Conductor.currentBeat % 4 == 0); } + // Show the mouse cursor. + // Just throwing this somewhere convenient and infrequently called because sometimes Flixel's debug thing hides the cursor. + Cursor.show(); + return true; } @@ -3015,6 +3040,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Update the event sprite's position. eventSprite.updateEventPosition(renderedEvents); + // Update the sprite's graphic. TODO: Is this inefficient? + eventSprite.playAnimation(eventSprite.eventData.event); } else { @@ -3471,6 +3498,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // trace('shouldHandleCursor: $shouldHandleCursor'); + // TODO: TBH some of this should be using FlxMouseEventManager... + if (shouldHandleCursor) { // Over the course of this big conditional block, @@ -4054,14 +4083,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { // Create an event and place it in the chart. // TODO: Figure out configuring event data. - var newEventData:SongEventData = new SongEventData(cursorSnappedMs, selectedEventKind, selectedEventData.clone()); + var newEventData:SongEventData = new SongEventData(cursorSnappedMs, eventKindToPlace, eventDataToPlace.clone()); performCommand(new AddEventsCommand([newEventData], FlxG.keys.pressed.CONTROL)); } else { // Create a note and place it in the chart. - var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, selectedNoteKind.clone()); + var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace.clone()); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); @@ -4099,13 +4128,52 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (highlightedNote != null && highlightedNote.noteData != null) { // TODO: Handle the case of clicking on a sustain piece. - // Remove the note. - performCommand(new RemoveNotesCommand([highlightedNote.noteData])); + if (FlxG.keys.pressed.SHIFT) + { + // Shift + Right click opens the context menu. + // If we are clicking a large selection, open the Selection context menu, otherwise open the Note context menu. + var isHighlightedNoteSelected:Bool = isNoteSelected(highlightedNote.noteData); + var useSingleNoteContextMenu:Bool = (!isHighlightedNoteSelected) + || (isHighlightedNoteSelected && currentNoteSelection.length == 1); + // Show the context menu connected to the note. + if (useSingleNoteContextMenu) + { + this.openNoteContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedNote.noteData); + } + else + { + this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY); + } + } + else + { + // Right click removes the note. + performCommand(new RemoveNotesCommand([highlightedNote.noteData])); + } } else if (highlightedEvent != null && highlightedEvent.eventData != null) { - // Remove the event. - performCommand(new RemoveEventsCommand([highlightedEvent.eventData])); + if (FlxG.keys.pressed.SHIFT) + { + // Shift + Right click opens the context menu. + // If we are clicking a large selection, open the Selection context menu, otherwise open the Event context menu. + var isHighlightedEventSelected:Bool = isEventSelected(highlightedEvent.eventData); + var useSingleEventContextMenu:Bool = (!isHighlightedEventSelected) + || (isHighlightedEventSelected && currentEventSelection.length == 1); + if (useSingleEventContextMenu) + { + this.openEventContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY, highlightedEvent.eventData); + } + else + { + this.openSelectionContextMenu(FlxG.mouse.screenX, FlxG.mouse.screenY); + } + } + else + { + // Right click removes the event. + performCommand(new RemoveEventsCommand([highlightedEvent.eventData])); + } } else { @@ -4126,11 +4194,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (gridGhostEvent == null) throw "ERROR: Tried to handle cursor, but gridGhostEvent is null! Check ChartEditorState.buildGrid()"; - var eventData:SongEventData = gridGhostEvent.eventData != null ? gridGhostEvent.eventData : new SongEventData(cursorMs, selectedEventKind, null); + var eventData:SongEventData = gridGhostEvent.eventData != null ? gridGhostEvent.eventData : new SongEventData(cursorMs, eventKindToPlace, null); - if (selectedEventKind != eventData.event) + if (eventKindToPlace != eventData.event) { - eventData.event = selectedEventKind; + eventData.event = eventKindToPlace; } eventData.time = cursorSnappedMs; @@ -4146,11 +4214,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (gridGhostNote == null) throw "ERROR: Tried to handle cursor, but gridGhostNote is null! Check ChartEditorState.buildGrid()"; - var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, selectedNoteKind); + var noteData:SongNoteData = gridGhostNote.noteData != null ? gridGhostNote.noteData : new SongNoteData(cursorMs, cursorColumn, 0, noteKindToPlace); - if (cursorColumn != noteData.data || selectedNoteKind != noteData.kind) + if (cursorColumn != noteData.data || noteKindToPlace != noteData.kind) { - noteData.kind = selectedNoteKind; + noteData.kind = noteKindToPlace; noteData.data = cursorColumn; gridGhostNote.playNoteAnimation(); } @@ -4443,7 +4511,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (notesAtPos.length == 0) { - var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, selectedNoteKind); + var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); } else @@ -4746,10 +4814,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState #end } - override function handleQuickWatch():Void + function handleQuickWatch():Void { - super.handleQuickWatch(); - FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0); FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels); @@ -5487,6 +5553,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState cleanupAutoSave(); + this.closeAllMenus(); + // Hide the mouse cursor on other states. Cursor.hide(); diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx index abe8b9e35..f59672646 100644 --- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx @@ -33,6 +33,16 @@ class SelectItemsCommand implements ChartEditorCommand state.currentEventSelection.push(event); } + // If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event. + if (this.notes.length == 0 && this.events.length >= 1) + { + var eventSelected = this.events[0]; + state.eventKindToPlace = eventSelected.event; + var eventData = eventSelected.valueAsStruct(); + state.eventDataToPlace = eventData; + state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); + } + state.noteDisplayDirty = true; state.notePreviewDirty = true; } diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index a06aefabc..7dc344835 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -30,6 +30,16 @@ class SetItemSelectionCommand implements ChartEditorCommand state.currentNoteSelection = notes; state.currentEventSelection = events; + // If we just selected one or more events (and no notes), then we should make the event data toolbox display the event data for the selected event. + if (this.notes.length == 0 && this.events.length >= 1) + { + var eventSelected = this.events[0]; + state.eventKindToPlace = eventSelected.event; + var eventData = eventSelected.valueAsStruct(); + state.eventDataToPlace = eventData; + state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); + } + state.noteDisplayDirty = true; } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx index 4c9d91407..e5bbf0807 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorEventSprite.hx @@ -145,8 +145,6 @@ class ChartEditorEventSprite extends FlxSprite else { this.visible = true; - // Only play the animation if the event type has changed. - // if (this.eventData == null || this.eventData.event != value.event) playAnimation(value.event); this.eventData = value; // Update the position to match the note data. diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx new file mode 100644 index 000000000..f25f3ebb3 --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorBaseContextMenu.hx @@ -0,0 +1,19 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; + +@:access(funkin.ui.debug.charting.ChartEditorState) +class ChartEditorBaseContextMenu extends Menu +{ + var chartEditorState:ChartEditorState; + + public function new(chartEditorState:ChartEditorState, xPos:Float = 0, yPos:Float = 0) + { + super(); + + this.chartEditorState = chartEditorState; + + this.left = xPos; + this.top = yPos; + } +} diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx new file mode 100644 index 000000000..9529cc2fd --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorDefaultContextMenu.hx @@ -0,0 +1,14 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; +import haxe.ui.core.Screen; + +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/default.xml")) +class ChartEditorDefaultContextMenu extends ChartEditorBaseContextMenu +{ + public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0) + { + super(chartEditorState2, xPos2, yPos2); + } +} diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx new file mode 100644 index 000000000..a79125b21 --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorEventContextMenu.hx @@ -0,0 +1,32 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; +import haxe.ui.containers.menus.MenuItem; +import haxe.ui.core.Screen; +import funkin.data.song.SongData.SongEventData; +import funkin.ui.debug.charting.commands.RemoveEventsCommand; + +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/event.xml")) +class ChartEditorEventContextMenu extends ChartEditorBaseContextMenu +{ + var contextmenuEdit:MenuItem; + var contextmenuDelete:MenuItem; + + var data:SongEventData; + + public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0, data:SongEventData) + { + super(chartEditorState2, xPos2, yPos2); + this.data = data; + + initialize(); + } + + function initialize() + { + contextmenuDelete.onClick = function(_) { + chartEditorState.performCommand(new RemoveEventsCommand([data])); + } + } +} diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx new file mode 100644 index 000000000..4bfab27e8 --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorNoteContextMenu.hx @@ -0,0 +1,38 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; +import haxe.ui.containers.menus.MenuItem; +import haxe.ui.core.Screen; +import funkin.data.song.SongData.SongNoteData; +import funkin.ui.debug.charting.commands.FlipNotesCommand; +import funkin.ui.debug.charting.commands.RemoveNotesCommand; + +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/note.xml")) +class ChartEditorNoteContextMenu extends ChartEditorBaseContextMenu +{ + var contextmenuFlip:MenuItem; + var contextmenuDelete:MenuItem; + + var data:SongNoteData; + + public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0, data:SongNoteData) + { + super(chartEditorState2, xPos2, yPos2); + this.data = data; + + initialize(); + } + + function initialize():Void + { + // NOTE: Remember to use commands here to ensure undo/redo works properly + contextmenuFlip.onClick = function(_) { + chartEditorState.performCommand(new FlipNotesCommand([data])); + } + + contextmenuDelete.onClick = function(_) { + chartEditorState.performCommand(new RemoveNotesCommand([data])); + } + } +} diff --git a/source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx b/source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx new file mode 100644 index 000000000..feed9b689 --- /dev/null +++ b/source/funkin/ui/debug/charting/contextmenus/ChartEditorSelectionContextMenu.hx @@ -0,0 +1,58 @@ +package funkin.ui.debug.charting.contextmenus; + +import haxe.ui.containers.menus.Menu; +import haxe.ui.containers.menus.MenuItem; +import haxe.ui.core.Screen; +import funkin.ui.debug.charting.commands.CutItemsCommand; +import funkin.ui.debug.charting.commands.RemoveEventsCommand; +import funkin.ui.debug.charting.commands.RemoveItemsCommand; +import funkin.ui.debug.charting.commands.RemoveNotesCommand; + +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/context-menus/selection.xml")) +class ChartEditorSelectionContextMenu extends ChartEditorBaseContextMenu +{ + var contextmenuCut:MenuItem; + var contextmenuCopy:MenuItem; + var contextmenuPaste:MenuItem; + var contextmenuDelete:MenuItem; + var contextmenuFlip:MenuItem; + var contextmenuSelectAll:MenuItem; + var contextmenuSelectInverse:MenuItem; + var contextmenuSelectNone:MenuItem; + + public function new(chartEditorState2:ChartEditorState, xPos2:Float = 0, yPos2:Float = 0) + { + super(chartEditorState2, xPos2, yPos2); + + initialize(); + } + + function initialize():Void + { + contextmenuCut.onClick = (_) -> { + chartEditorState.performCommand(new CutItemsCommand(chartEditorState.currentNoteSelection, chartEditorState.currentEventSelection)); + }; + contextmenuCopy.onClick = (_) -> { + chartEditorState.copySelection(); + }; + contextmenuFlip.onClick = (_) -> { + if (chartEditorState.currentNoteSelection.length > 0 && chartEditorState.currentEventSelection.length > 0) + { + chartEditorState.performCommand(new RemoveItemsCommand(chartEditorState.currentNoteSelection, chartEditorState.currentEventSelection)); + } + else if (chartEditorState.currentNoteSelection.length > 0) + { + chartEditorState.performCommand(new RemoveNotesCommand(chartEditorState.currentNoteSelection)); + } + else if (chartEditorState.currentEventSelection.length > 0) + { + chartEditorState.performCommand(new RemoveEventsCommand(chartEditorState.currentEventSelection)); + } + else + { + // Do nothing??? + } + }; + } +} diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx new file mode 100644 index 000000000..b914f4149 --- /dev/null +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorContextMenuHandler.hx @@ -0,0 +1,64 @@ +package funkin.ui.debug.charting.handlers; + +import funkin.ui.debug.charting.contextmenus.ChartEditorDefaultContextMenu; +import funkin.ui.debug.charting.contextmenus.ChartEditorEventContextMenu; +import funkin.ui.debug.charting.contextmenus.ChartEditorNoteContextMenu; +import funkin.ui.debug.charting.contextmenus.ChartEditorSelectionContextMenu; +import haxe.ui.containers.menus.Menu; +import haxe.ui.core.Screen; +import funkin.data.song.SongData.SongNoteData; +import funkin.data.song.SongData.SongEventData; + +/** + * Handles context menus (the little menus that appear when you right click on stuff) for the new Chart Editor. + */ +@:nullSafety +@:access(funkin.ui.debug.charting.ChartEditorState) +class ChartEditorContextMenuHandler +{ + static var existingMenus:Array = []; + + public static function openDefaultContextMenu(state:ChartEditorState, xPos:Float, yPos:Float) + { + displayMenu(state, new ChartEditorDefaultContextMenu(state, xPos, yPos)); + } + + public static function openSelectionContextMenu(state:ChartEditorState, xPos:Float, yPos:Float) + { + displayMenu(state, new ChartEditorSelectionContextMenu(state, xPos, yPos)); + } + + public static function openNoteContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongNoteData) + { + displayMenu(state, new ChartEditorNoteContextMenu(state, xPos, yPos, data)); + } + + public static function openEventContextMenu(state:ChartEditorState, xPos:Float, yPos:Float, data:SongEventData) + { + displayMenu(state, new ChartEditorEventContextMenu(state, xPos, yPos, data)); + } + + static function displayMenu(state:ChartEditorState, targetMenu:Menu) + { + // Close any existing menus + closeAllMenus(state); + + // Show the new menu + Screen.instance.addComponent(targetMenu); + existingMenus.push(targetMenu); + } + + public static function closeMenu(state:ChartEditorState, targetMenu:Menu) + { + // targetMenu.close(); + existingMenus.remove(targetMenu); + } + + public static function closeAllMenus(state:ChartEditorState) + { + for (existingMenu in existingMenus) + { + closeMenu(state, existingMenu); + } + } +} diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx index a9a9c375d..08298eb66 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx @@ -23,6 +23,7 @@ import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.ui.haxeui.components.CharacterPlayer; import funkin.util.FileUtil; import haxe.ui.components.Button; +import haxe.ui.data.ArrayDataSource; import haxe.ui.components.CheckBox; import haxe.ui.components.DropDown; import haxe.ui.components.HorizontalSlider; @@ -36,12 +37,12 @@ import haxe.ui.containers.dialogs.Dialog.DialogButton; import haxe.ui.containers.dialogs.Dialog.DialogEvent; import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox; import funkin.ui.debug.charting.toolboxes.ChartEditorMetadataToolbox; +import funkin.ui.debug.charting.toolboxes.ChartEditorEventDataToolbox; import haxe.ui.containers.Frame; import haxe.ui.containers.Grid; import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeViewNode; import haxe.ui.core.Component; -import haxe.ui.data.ArrayDataSource; import haxe.ui.events.UIEvent; /** @@ -79,8 +80,9 @@ class ChartEditorToolboxHandler { case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT: onShowToolboxNoteData(state, toolbox); - case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT: - onShowToolboxEventData(state, toolbox); + case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT: + // TODO: Fix this. + cast(toolbox, ChartEditorBaseToolbox).refresh(); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT: onShowToolboxPlaytestProperties(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT: @@ -119,7 +121,7 @@ class ChartEditorToolboxHandler { case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT: onHideToolboxNoteData(state, toolbox); - case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT: + case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT: onHideToolboxEventData(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT: onHideToolboxPlaytestProperties(state, toolbox); @@ -195,7 +197,7 @@ class ChartEditorToolboxHandler { case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT: toolbox = buildToolboxNoteDataLayout(state); - case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT: + case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT: toolbox = buildToolboxEventDataLayout(state); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT: toolbox = buildToolboxPlaytestPropertiesLayout(state); @@ -283,19 +285,19 @@ class ChartEditorToolboxHandler toolboxNotesCustomKindLabel.hidden = false; toolboxNotesCustomKind.hidden = false; - state.selectedNoteKind = toolboxNotesCustomKind.text; + state.noteKindToPlace = toolboxNotesCustomKind.text; } else { toolboxNotesCustomKindLabel.hidden = true; toolboxNotesCustomKind.hidden = true; - state.selectedNoteKind = event.data.id; + state.noteKindToPlace = event.data.id; } } toolboxNotesCustomKind.onChange = function(event:UIEvent) { - state.selectedNoteKind = toolboxNotesCustomKind.text; + state.noteKindToPlace = toolboxNotesCustomKind.text; } return toolbox; @@ -305,159 +307,16 @@ class ChartEditorToolboxHandler static function onHideToolboxNoteData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - static function buildToolboxEventDataLayout(state:ChartEditorState):Null - { - var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT); - - if (toolbox == null) return null; - - // Starting position. - toolbox.x = 100; - toolbox.y = 150; - - toolbox.onDialogClosed = function(event:DialogEvent) { - state.menubarItemToggleToolboxEvents.selected = false; - } - - var toolboxEventsEventKind:Null = toolbox.findComponent('toolboxEventsEventKind', DropDown); - if (toolboxEventsEventKind == null) throw 'ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Could not find toolboxEventsEventKind component.'; - var toolboxEventsDataGrid:Null = toolbox.findComponent('toolboxEventsDataGrid', Grid); - if (toolboxEventsDataGrid == null) throw 'ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Could not find toolboxEventsDataGrid component.'; - - toolboxEventsEventKind.dataSource = new ArrayDataSource(); - - var songEvents:Array = SongEventParser.listEvents(); - - for (event in songEvents) - { - toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id}); - } - - toolboxEventsEventKind.onChange = function(event:UIEvent) { - var eventType:String = event.data.value; - - trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType'); - - state.selectedEventKind = eventType; - - var schema:SongEventSchema = SongEventParser.getEventSchema(eventType); - - if (schema == null) - { - trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Unknown event kind: $eventType'); - return; - } - - buildEventDataFormFromSchema(state, toolboxEventsDataGrid, schema); - } - toolboxEventsEventKind.value = state.selectedEventKind; - - return toolbox; - } - - static function onShowToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - - static function onShowToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + static function onHideToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} static function onHideToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + static function onHideToolboxDifficulty(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + + static function onShowToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + static function onHideToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - static function buildEventDataFormFromSchema(state:ChartEditorState, target:Box, schema:SongEventSchema):Void - { - trace(schema); - // Clear the frame. - target.removeAllComponents(); - - state.selectedEventData = {}; - - for (field in schema) - { - if (field == null) continue; - - // Add a label. - var label:Label = new Label(); - label.text = field.title; - label.verticalAlign = "center"; - target.addComponent(label); - - var input:Component; - switch (field.type) - { - case INTEGER: - var numberStepper:NumberStepper = new NumberStepper(); - numberStepper.id = field.name; - numberStepper.step = field.step ?? 1.0; - numberStepper.min = field.min ?? 0.0; - numberStepper.max = field.max ?? 10.0; - if (field.defaultValue != null) numberStepper.value = field.defaultValue; - input = numberStepper; - case FLOAT: - var numberStepper:NumberStepper = new NumberStepper(); - numberStepper.id = field.name; - numberStepper.step = field.step ?? 0.1; - if (field.min != null) numberStepper.min = field.min; - if (field.max != null) numberStepper.max = field.max; - if (field.defaultValue != null) numberStepper.value = field.defaultValue; - input = numberStepper; - case BOOL: - var checkBox:CheckBox = new CheckBox(); - checkBox.id = field.name; - if (field.defaultValue != null) checkBox.selected = field.defaultValue; - input = checkBox; - case ENUM: - var dropDown:DropDown = new DropDown(); - dropDown.id = field.name; - dropDown.width = 200.0; - dropDown.dataSource = new ArrayDataSource(); - - if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.'; - - // Add entries to the dropdown. - - for (optionName in field.keys.keys()) - { - var optionValue:Null = field.keys.get(optionName); - trace('$optionName : $optionValue'); - dropDown.dataSource.add({value: optionValue, text: optionName}); - } - - dropDown.value = field.defaultValue; - - input = dropDown; - case STRING: - input = new TextField(); - input.id = field.name; - if (field.defaultValue != null) input.text = field.defaultValue; - default: - // Unknown type. Display a label so we know what it is. - input = new Label(); - input.id = field.name; - input.text = field.type; - } - - target.addComponent(input); - - input.onChange = function(event:UIEvent) { - var value = event.target.value; - if (field.type == ENUM) - { - value = event.target.value.value; - } - trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${value}'); - - if (value == null) - { - state.selectedEventData.remove(event.target.id); - } - else - { - state.selectedEventData.set(event.target.id, value); - } - } - } - } - static function buildToolboxPlaytestPropertiesLayout(state:ChartEditorState):Null { // fill with playtest properties @@ -576,8 +435,6 @@ class ChartEditorToolboxHandler trace('selected node: ${treeView.selectedNode}'); } - static function onHideToolboxDifficulty(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} - static function buildToolboxMetadataLayout(state:ChartEditorState):Null { var toolbox:ChartEditorBaseToolbox = ChartEditorMetadataToolbox.build(state); @@ -587,7 +444,14 @@ class ChartEditorToolboxHandler return toolbox; } - static function onHideToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} + static function buildToolboxEventDataLayout(state:ChartEditorState):Null + { + var toolbox:ChartEditorBaseToolbox = ChartEditorEventDataToolbox.build(state); + + if (toolbox == null) return null; + + return toolbox; + } static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Null { diff --git a/source/funkin/ui/debug/charting/import.hx b/source/funkin/ui/debug/charting/import.hx index 933eaa3a5..b0569e3bb 100644 --- a/source/funkin/ui/debug/charting/import.hx +++ b/source/funkin/ui/debug/charting/import.hx @@ -3,6 +3,7 @@ package funkin.ui.debug.charting; #if !macro // Apply handlers so they can be called as though they were functions in ChartEditorState using funkin.ui.debug.charting.handlers.ChartEditorAudioHandler; +using funkin.ui.debug.charting.handlers.ChartEditorContextMenuHandler; using funkin.ui.debug.charting.handlers.ChartEditorDialogHandler; using funkin.ui.debug.charting.handlers.ChartEditorImportExportHandler; using funkin.ui.debug.charting.handlers.ChartEditorNotificationHandler; diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx new file mode 100644 index 000000000..7066fc913 --- /dev/null +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -0,0 +1,259 @@ +package funkin.ui.debug.charting.toolboxes; + +import funkin.play.character.BaseCharacter.CharacterType; +import funkin.play.character.CharacterData; +import funkin.play.stage.StageData; +import funkin.play.event.SongEvent; +import funkin.data.event.SongEventData.SongEventSchema; +import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand; +import funkin.ui.debug.charting.util.ChartEditorDropdowns; +import haxe.ui.components.Button; +import haxe.ui.components.CheckBox; +import haxe.ui.components.DropDown; +import haxe.ui.components.HorizontalSlider; +import haxe.ui.components.Label; +import haxe.ui.components.NumberStepper; +import haxe.ui.components.Slider; +import haxe.ui.core.Component; +import funkin.data.event.SongEventData.SongEventParser; +import haxe.ui.components.TextField; +import haxe.ui.containers.Box; +import haxe.ui.containers.Frame; +import haxe.ui.events.UIEvent; +import haxe.ui.data.ArrayDataSource; +import haxe.ui.containers.Grid; +import haxe.ui.components.DropDown; +import haxe.ui.containers.Frame; + +/** + * The toolbox which allows modifying information like Song Title, Scroll Speed, Characters/Stages, and starting BPM. + */ +// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros. +@:access(funkin.ui.debug.charting.ChartEditorState) +@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/toolboxes/event-data.xml")) +class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox +{ + var toolboxEventsEventKind:DropDown; + var toolboxEventsDataFrame:Frame; + var toolboxEventsDataGrid:Grid; + + var _initializing:Bool = true; + + public function new(chartEditorState2:ChartEditorState) + { + super(chartEditorState2); + + initialize(); + + this.onDialogClosed = onClose; + + this._initializing = false; + } + + function onClose(event:UIEvent) + { + chartEditorState.menubarItemToggleToolboxEventData.selected = false; + } + + function initialize():Void + { + toolboxEventsEventKind.dataSource = new ArrayDataSource(); + + var songEvents:Array = SongEventParser.listEvents(); + + for (event in songEvents) + { + toolboxEventsEventKind.dataSource.add({text: event.getTitle(), value: event.id}); + } + + toolboxEventsEventKind.onChange = function(event:UIEvent) { + var eventType:String = event.data.value; + + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Event type changed: $eventType'); + + // Edit the event data to place. + chartEditorState.eventKindToPlace = eventType; + + var schema:SongEventSchema = SongEventParser.getEventSchema(eventType); + + if (schema == null) + { + trace('ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Unknown event kind: $eventType'); + return; + } + + buildEventDataFormFromSchema(toolboxEventsDataGrid, schema); + + if (!_initializing && chartEditorState.currentEventSelection.length > 0) + { + // Edit the event data of any selected events. + for (event in chartEditorState.currentEventSelection) + { + event.event = chartEditorState.eventKindToPlace; + event.value = chartEditorState.eventDataToPlace; + } + chartEditorState.saveDataDirty = true; + chartEditorState.noteDisplayDirty = true; + chartEditorState.notePreviewDirty = true; + } + } + toolboxEventsEventKind.value = chartEditorState.eventKindToPlace; + } + + public override function refresh():Void + { + super.refresh(); + + toolboxEventsEventKind.value = chartEditorState.eventKindToPlace; + + for (pair in chartEditorState.eventDataToPlace.keyValueIterator()) + { + var fieldId:String = pair.key; + var value:Null = pair.value; + + var field:Component = toolboxEventsDataGrid.findComponent(fieldId); + + if (field == null) + { + throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" does not exist in the event data form.'; + } + else + { + switch (field) + { + case Std.isOfType(_, NumberStepper) => true: + var numberStepper:NumberStepper = cast field; + numberStepper.value = value; + case Std.isOfType(_, CheckBox) => true: + var checkBox:CheckBox = cast field; + checkBox.selected = value; + case Std.isOfType(_, DropDown) => true: + var dropDown:DropDown = cast field; + dropDown.value = value; + case Std.isOfType(_, TextField) => true: + var textField:TextField = cast field; + textField.text = value; + default: + throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" is of unknown type "${Type.getClassName(Type.getClass(field))}".'; + } + } + } + } + + function buildEventDataFormFromSchema(target:Box, schema:SongEventSchema):Void + { + trace(schema); + // Clear the frame. + target.removeAllComponents(); + + chartEditorState.eventDataToPlace = {}; + + for (field in schema) + { + if (field == null) continue; + + // Add a label for the data field. + var label:Label = new Label(); + label.text = field.title; + label.verticalAlign = "center"; + target.addComponent(label); + + // Add an input field for the data field. + var input:Component; + switch (field.type) + { + case INTEGER: + var numberStepper:NumberStepper = new NumberStepper(); + numberStepper.id = field.name; + numberStepper.step = field.step ?? 1.0; + numberStepper.min = field.min ?? 0.0; + numberStepper.max = field.max ?? 10.0; + if (field.defaultValue != null) numberStepper.value = field.defaultValue; + input = numberStepper; + case FLOAT: + var numberStepper:NumberStepper = new NumberStepper(); + numberStepper.id = field.name; + numberStepper.step = field.step ?? 0.1; + if (field.min != null) numberStepper.min = field.min; + if (field.max != null) numberStepper.max = field.max; + if (field.defaultValue != null) numberStepper.value = field.defaultValue; + input = numberStepper; + case BOOL: + var checkBox:CheckBox = new CheckBox(); + checkBox.id = field.name; + if (field.defaultValue != null) checkBox.selected = field.defaultValue; + input = checkBox; + case ENUM: + var dropDown:DropDown = new DropDown(); + dropDown.id = field.name; + dropDown.width = 200.0; + dropDown.dataSource = new ArrayDataSource(); + + if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.'; + + // Add entries to the dropdown. + + for (optionName in field.keys.keys()) + { + var optionValue:Null = field.keys.get(optionName); + trace('$optionName : $optionValue'); + dropDown.dataSource.add({value: optionValue, text: optionName}); + } + + dropDown.value = field.defaultValue; + + input = dropDown; + case STRING: + input = new TextField(); + input.id = field.name; + if (field.defaultValue != null) input.text = field.defaultValue; + default: + // Unknown type. Display a label that proclaims the type so we can debug it. + input = new Label(); + input.id = field.name; + input.text = field.type; + } + + target.addComponent(input); + + // Update the value of the event data. + input.onChange = function(event:UIEvent) { + var value = event.target.value; + if (field.type == ENUM) + { + value = event.target.value.value; + } + + trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${value}'); + + // Edit the event data to place. + if (value == null) + { + chartEditorState.eventDataToPlace.remove(event.target.id); + } + else + { + chartEditorState.eventDataToPlace.set(event.target.id, value); + } + + // Edit the event data of any existing events. + if (!_initializing && chartEditorState.currentEventSelection.length > 0) + { + for (event in chartEditorState.currentEventSelection) + { + event.event = chartEditorState.eventKindToPlace; + event.value = chartEditorState.eventDataToPlace; + } + chartEditorState.saveDataDirty = true; + chartEditorState.noteDisplayDirty = true; + chartEditorState.notePreviewDirty = true; + } + } + } + } + + public static function build(chartEditorState:ChartEditorState):ChartEditorEventDataToolbox + { + return new ChartEditorEventDataToolbox(chartEditorState); + } +} diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx index bc9384cf3..3f3c825ce 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorMetadataToolbox.hx @@ -162,6 +162,8 @@ class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox public override function refresh():Void { + super.refresh(); + inputSongName.value = chartEditorState.currentSongMetadata.songName; inputSongArtist.value = chartEditorState.currentSongMetadata.artist; inputStage.value = chartEditorState.currentSongMetadata.playData.stage; diff --git a/source/funkin/util/plugins/EvacuateDebugPlugin.hx b/source/funkin/util/plugins/EvacuateDebugPlugin.hx new file mode 100644 index 000000000..1803c25ba --- /dev/null +++ b/source/funkin/util/plugins/EvacuateDebugPlugin.hx @@ -0,0 +1,35 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which adds functionality to press `F4` to immediately transition to the main menu. + * This is useful for debugging or if you get softlocked or something. + */ +class EvacuateDebugPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new EvacuateDebugPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (FlxG.keys.justPressed.F4) + { + FlxG.switchState(new funkin.ui.mainmenu.MainMenuState()); + } + } + + public override function destroy():Void + { + super.destroy(); + } +} diff --git a/source/funkin/util/plugins/README.md b/source/funkin/util/plugins/README.md new file mode 100644 index 000000000..fe87d36e5 --- /dev/null +++ b/source/funkin/util/plugins/README.md @@ -0,0 +1,5 @@ +# funkin.util.plugins + +Flixel plugins are objects with `update()` functions that are called from every state. + +See: https://github.com/HaxeFlixel/flixel/blob/dev/flixel/system/frontEnds/PluginFrontEnd.hx diff --git a/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx new file mode 100644 index 000000000..a43317cce --- /dev/null +++ b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx @@ -0,0 +1,38 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which adds functionality to press `F5` to reload all game assets, then reload the current state. + * This is useful for hot reloading assets during development. + */ +class ReloadAssetsDebugPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new ReloadAssetsDebugPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (FlxG.keys.justPressed.F5) + { + funkin.modding.PolymodHandler.forceReloadAssets(); + + // Create a new instance of the current state, so old data is cleared. + FlxG.resetState(); + } + } + + public override function destroy():Void + { + super.destroy(); + } +} diff --git a/source/funkin/util/plugins/WatchPlugin.hx b/source/funkin/util/plugins/WatchPlugin.hx new file mode 100644 index 000000000..5d718c2e0 --- /dev/null +++ b/source/funkin/util/plugins/WatchPlugin.hx @@ -0,0 +1,38 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which adds functionality to display several universally important values + * in the Flixel variable watch window. + */ +class WatchPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new WatchPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + FlxG.watch.addQuick("songPosition", Conductor.songPosition); + FlxG.watch.addQuick("songPositionNoOffset", Conductor.songPosition + Conductor.instrumentalOffset); + FlxG.watch.addQuick("musicTime", FlxG.sound?.music?.time ?? 0.0); + FlxG.watch.addQuick("bpm", Conductor.bpm); + FlxG.watch.addQuick("currentMeasureTime", Conductor.currentMeasureTime); + FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime); + FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); + } + + public override function destroy():Void + { + super.destroy(); + } +}