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

Work in progress on context menus and song event editing.

This commit is contained in:
EliteMasterEric 2024-01-03 19:53:17 -05:00
parent 43872bb213
commit bbaa9aa4af
24 changed files with 804 additions and 236 deletions

2
assets

@ -1 +1 @@
Subproject commit 6f17eb051e2609d59a591d4e6eb78e37c6e90adb
Subproject commit 23f85072c190373592a30aed137b034715623f28

View file

@ -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"
},
{

View file

@ -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
//

View file

@ -240,4 +240,28 @@ typedef SongEventSchemaField =
?defaultValue:Dynamic,
}
typedef SongEventSchema = Array<SongEventSchemaField>;
@:forward
abstract SongEventSchema(SongEventSchemaRaw)
{
public function new(?fields:Array<SongEventSchemaField>)
{
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<SongEventSchemaField>;

View file

@ -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<Dynamic> = {};
result.set(defaultKey, this.value);
return cast result;
}
else
{
return cast this.value;
}
}
public inline function getDynamic(key:String):Null<Dynamic>
{
return this.value == null ? null : Reflect.field(this.value, key);

View file

@ -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);

View file

@ -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<Dynamic> = {};
var eventDataToPlace:DynamicAccess<Dynamic> = {};
/**
* 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<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),
});
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<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),
});
}
/**
* 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();

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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.

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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]));
}
}
}

View file

@ -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]));
}
}
}

View file

@ -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???
}
};
}
}

View file

@ -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<Menu> = [];
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);
}
}
}

View file

@ -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<CollapsibleDialog>
{
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<DropDown> = toolbox.findComponent('toolboxEventsEventKind', DropDown);
if (toolboxEventsEventKind == null) throw 'ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Could not find toolboxEventsEventKind component.';
var toolboxEventsDataGrid:Null<Grid> = toolbox.findComponent('toolboxEventsDataGrid', Grid);
if (toolboxEventsDataGrid == null) throw 'ChartEditorToolboxHandler.buildToolboxEventDataLayout() - Could not find toolboxEventsDataGrid component.';
toolboxEventsEventKind.dataSource = new ArrayDataSource();
var songEvents:Array<SongEvent> = 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<Dynamic> = 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<CollapsibleDialog>
{
// 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<ChartEditorBaseToolbox>
{
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<ChartEditorBaseToolbox>
{
var toolbox:ChartEditorBaseToolbox = ChartEditorEventDataToolbox.build(state);
if (toolbox == null) return null;
return toolbox;
}
static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Null<CollapsibleDialog>
{

View file

@ -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;

View file

@ -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<SongEvent> = 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<Dynamic> = 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<Dynamic> = 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);
}
}

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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

View file

@ -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();
}
}

View file

@ -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();
}
}