mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2025-04-01 01:39:44 -04:00
Merge pull request #266 from FunkinCrew/feature/chart-editor-context-menus
Chart Editor: Context menus and song event editing.
This commit is contained in:
commit
b6b0f9fa46
31 changed files with 978 additions and 327 deletions
assets
source/funkin
InitState.hx
data
modding
play
PlayState.hx
event
ui
MusicBeatState.hx
debug/charting
ChartEditorState.hx
commands
components
contextmenus
ChartEditorBaseContextMenu.hxChartEditorDefaultContextMenu.hxChartEditorEventContextMenu.hxChartEditorNoteContextMenu.hxChartEditorSelectionContextMenu.hx
handlers
import.hxtoolboxes
util/plugins
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit b551cb29078e3599a5d608a22238450f9380a3fc
|
||||
Subproject commit 4246be3aa353e43772760d02ae9ff262718dee06
|
|
@ -19,7 +19,7 @@ import funkin.play.PlayStatePlaylist;
|
|||
import openfl.display.BitmapData;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.play.cutscene.dialogue.ConversationDataParser;
|
||||
import funkin.play.cutscene.dialogue.DialogueBoxDataParser;
|
||||
import funkin.play.cutscene.dialogue.SpeakerDataParser;
|
||||
|
@ -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
|
||||
//
|
||||
|
@ -206,7 +213,7 @@ class InitState extends FlxState
|
|||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
SongEventParser.loadEventCache();
|
||||
SongEventRegistry.loadEventCache();
|
||||
ConversationDataParser.loadConversationCache();
|
||||
DialogueBoxDataParser.loadDialogueBoxCache();
|
||||
SpeakerDataParser.loadSpeakerCache();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package funkin.data.event;
|
||||
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.util.macro.ClassMacro;
|
||||
import funkin.play.event.ScriptedSongEvent;
|
||||
|
@ -9,7 +9,7 @@ import funkin.play.event.ScriptedSongEvent;
|
|||
/**
|
||||
* This class statically handles the parsing of internal and scripted song event handlers.
|
||||
*/
|
||||
class SongEventParser
|
||||
class SongEventRegistry
|
||||
{
|
||||
/**
|
||||
* Every built-in event class must be added to this list.
|
||||
|
@ -160,84 +160,3 @@ class SongEventParser
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum abstract SongEventFieldType(String) from String to String
|
||||
{
|
||||
/**
|
||||
* The STRING type will display as a text field.
|
||||
*/
|
||||
var STRING = "string";
|
||||
|
||||
/**
|
||||
* The INTEGER type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var INTEGER = "integer";
|
||||
|
||||
/**
|
||||
* The FLOAT type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var FLOAT = "float";
|
||||
|
||||
/**
|
||||
* The BOOL type will display as a checkbox.
|
||||
*/
|
||||
var BOOL = "bool";
|
||||
|
||||
/**
|
||||
* The ENUM type will display as a dropdown.
|
||||
* Make sure to specify the `keys` field in the schema.
|
||||
*/
|
||||
var ENUM = "enum";
|
||||
}
|
||||
|
||||
typedef SongEventSchemaField =
|
||||
{
|
||||
/**
|
||||
* The name of the property as it should be saved in the event data.
|
||||
*/
|
||||
name:String,
|
||||
|
||||
/**
|
||||
* The title of the field to display in the UI.
|
||||
*/
|
||||
title:String,
|
||||
|
||||
/**
|
||||
* The type of the field.
|
||||
*/
|
||||
type:SongEventFieldType,
|
||||
|
||||
/**
|
||||
* Used only for ENUM values.
|
||||
* The key is the display name and the value is the actual value.
|
||||
*/
|
||||
?keys:Map<String, Dynamic>,
|
||||
|
||||
/**
|
||||
* Used for INTEGER and FLOAT values.
|
||||
* The minimum value that can be entered.
|
||||
* @default No minimum
|
||||
*/
|
||||
?min:Float,
|
||||
|
||||
/**
|
||||
* Used for INTEGER and FLOAT values.
|
||||
* The maximum value that can be entered.
|
||||
* @default No maximum
|
||||
*/
|
||||
?max:Float,
|
||||
|
||||
/**
|
||||
* Used for INTEGER and FLOAT values.
|
||||
* The step value that will be used when incrementing/decrementing the value.
|
||||
* @default `0.1`
|
||||
*/
|
||||
?step:Float,
|
||||
|
||||
/**
|
||||
* An optional default value for the field.
|
||||
*/
|
||||
?defaultValue:Dynamic,
|
||||
}
|
||||
|
||||
typedef SongEventSchema = Array<SongEventSchemaField>;
|
125
source/funkin/data/event/SongEventSchema.hx
Normal file
125
source/funkin/data/event/SongEventSchema.hx
Normal file
|
@ -0,0 +1,125 @@
|
|||
package funkin.data.event;
|
||||
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.util.macro.ClassMacro;
|
||||
import funkin.play.event.ScriptedSongEvent;
|
||||
|
||||
@:forward(name, tittlte, type, keys, min, max, step, defaultValue, iterator)
|
||||
abstract SongEventSchema(SongEventSchemaRaw)
|
||||
{
|
||||
public function new(?fields:Array<SongEventSchemaField>)
|
||||
{
|
||||
this = fields;
|
||||
}
|
||||
|
||||
@:arrayAccess
|
||||
public inline function getByName(name:String):SongEventSchemaField
|
||||
{
|
||||
for (field in this)
|
||||
{
|
||||
if (field.name == name) return field;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getFirstField():SongEventSchemaField
|
||||
{
|
||||
return this[0];
|
||||
}
|
||||
|
||||
@:arrayAccess
|
||||
public inline function get(key:Int)
|
||||
{
|
||||
return this[key];
|
||||
}
|
||||
|
||||
@:arrayAccess
|
||||
public inline function arrayWrite(k:Int, v:SongEventSchemaField):SongEventSchemaField
|
||||
{
|
||||
return this[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
typedef SongEventSchemaRaw = Array<SongEventSchemaField>;
|
||||
|
||||
typedef SongEventSchemaField =
|
||||
{
|
||||
/**
|
||||
* The name of the property as it should be saved in the event data.
|
||||
*/
|
||||
name:String,
|
||||
|
||||
/**
|
||||
* The title of the field to display in the UI.
|
||||
*/
|
||||
title:String,
|
||||
|
||||
/**
|
||||
* The type of the field.
|
||||
*/
|
||||
type:SongEventFieldType,
|
||||
|
||||
/**
|
||||
* Used only for ENUM values.
|
||||
* The key is the display name and the value is the actual value.
|
||||
*/
|
||||
?keys:Map<String, Dynamic>,
|
||||
|
||||
/**
|
||||
* Used for INTEGER and FLOAT values.
|
||||
* The minimum value that can be entered.
|
||||
* @default No minimum
|
||||
*/
|
||||
?min:Float,
|
||||
|
||||
/**
|
||||
* Used for INTEGER and FLOAT values.
|
||||
* The maximum value that can be entered.
|
||||
* @default No maximum
|
||||
*/
|
||||
?max:Float,
|
||||
|
||||
/**
|
||||
* Used for INTEGER and FLOAT values.
|
||||
* The step value that will be used when incrementing/decrementing the value.
|
||||
* @default `0.1`
|
||||
*/
|
||||
?step:Float,
|
||||
|
||||
/**
|
||||
* An optional default value for the field.
|
||||
*/
|
||||
?defaultValue:Dynamic,
|
||||
}
|
||||
|
||||
enum abstract SongEventFieldType(String) from String to String
|
||||
{
|
||||
/**
|
||||
* The STRING type will display as a text field.
|
||||
*/
|
||||
var STRING = "string";
|
||||
|
||||
/**
|
||||
* The INTEGER type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var INTEGER = "integer";
|
||||
|
||||
/**
|
||||
* The FLOAT type will display as a text field that only accepts numbers.
|
||||
*/
|
||||
var FLOAT = "float";
|
||||
|
||||
/**
|
||||
* The BOOL type will display as a checkbox.
|
||||
*/
|
||||
var BOOL = "bool";
|
||||
|
||||
/**
|
||||
* The ENUM type will display as a dropdown.
|
||||
* Make sure to specify the `keys` field in the schema.
|
||||
*/
|
||||
var ENUM = "enum";
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package funkin.data.song;
|
||||
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import thx.semver.Version;
|
||||
import funkin.util.tools.ICloneable;
|
||||
|
@ -677,6 +679,33 @@ 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 {};
|
||||
if (Std.isOfType(this.value, Array))
|
||||
{
|
||||
var result:haxe.DynamicAccess<Dynamic> = {};
|
||||
result.set(defaultKey, this.value);
|
||||
return cast result;
|
||||
}
|
||||
else if (Reflect.isObject(this.value))
|
||||
{
|
||||
// We enter this case if the value is a struct.
|
||||
return cast this.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result:haxe.DynamicAccess<Dynamic> = {};
|
||||
result.set(defaultKey, this.value);
|
||||
return cast result;
|
||||
}
|
||||
}
|
||||
|
||||
public inline function getSchema():Null<SongEventSchema>
|
||||
{
|
||||
return SongEventRegistry.getEventSchema(this.event);
|
||||
}
|
||||
|
||||
public inline function getDynamic(key:String):Null<Dynamic>
|
||||
{
|
||||
return this.value == null ? null : Reflect.field(this.value, key);
|
||||
|
|
|
@ -8,7 +8,7 @@ import funkin.play.stage.StageData;
|
|||
import polymod.Polymod;
|
||||
import polymod.backends.PolymodAssets.PolymodAssetType;
|
||||
import polymod.format.ParseRules.TextFileFormat;
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.data.level.LevelRegistry;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
|
@ -271,7 +271,7 @@ class PolymodHandler
|
|||
SongRegistry.instance.loadEntries();
|
||||
LevelRegistry.instance.loadEntries();
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
SongEventParser.loadEventCache();
|
||||
SongEventRegistry.loadEventCache();
|
||||
ConversationDataParser.loadConversationCache();
|
||||
DialogueBoxDataParser.loadDialogueBoxCache();
|
||||
SpeakerDataParser.loadSpeakerCache();
|
||||
|
|
|
@ -42,7 +42,7 @@ import funkin.play.cutscene.dialogue.Conversation;
|
|||
import funkin.play.cutscene.dialogue.ConversationDataParser;
|
||||
import funkin.play.cutscene.VanillaCutscenes;
|
||||
import funkin.play.cutscene.VideoCutscene;
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import funkin.play.notes.NoteSprite;
|
||||
import funkin.play.notes.NoteDirection;
|
||||
import funkin.play.notes.Strumline;
|
||||
|
@ -942,7 +942,7 @@ class PlayState extends MusicBeatSubState
|
|||
// TODO: Check that these work even when songPosition is less than 0.
|
||||
if (songEvents != null && songEvents.length > 0)
|
||||
{
|
||||
var songEventsToActivate:Array<SongEventData> = SongEventParser.queryEvents(songEvents, Conductor.instance.songPosition);
|
||||
var songEventsToActivate:Array<SongEventData> = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition);
|
||||
|
||||
if (songEventsToActivate.length > 0)
|
||||
{
|
||||
|
@ -961,7 +961,7 @@ class PlayState extends MusicBeatSubState
|
|||
// Calling event.cancelEvent() skips the event. Neat!
|
||||
if (!eventEvent.eventCanceled)
|
||||
{
|
||||
SongEventParser.handleEvent(event);
|
||||
SongEventRegistry.handleEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1607,7 +1607,7 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
// Reset song events.
|
||||
songEvents = currentChart.getEvents();
|
||||
SongEventParser.resetEvents(songEvents);
|
||||
SongEventRegistry.resetEvents(songEvents);
|
||||
|
||||
// Reset the notes on each strumline.
|
||||
var playerNoteData:Array<SongNoteData> = [];
|
||||
|
|
|
@ -5,8 +5,8 @@ import funkin.data.song.SongData;
|
|||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventData.SongEventFieldType;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||
|
||||
/**
|
||||
* This class represents a handler for a type of song event.
|
||||
|
@ -132,7 +132,7 @@ class FocusCameraSongEvent extends SongEvent
|
|||
*/
|
||||
public override function getEventSchema():SongEventSchema
|
||||
{
|
||||
return [
|
||||
return new SongEventSchema([
|
||||
{
|
||||
name: "char",
|
||||
title: "Character",
|
||||
|
@ -154,6 +154,6 @@ class FocusCameraSongEvent extends SongEvent
|
|||
step: 10.0,
|
||||
type: SongEventFieldType.FLOAT,
|
||||
}
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import funkin.data.song.SongData;
|
|||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventData.SongEventFieldType;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||
|
||||
class PlayAnimationSongEvent extends SongEvent
|
||||
{
|
||||
|
@ -89,7 +89,7 @@ class PlayAnimationSongEvent extends SongEvent
|
|||
*/
|
||||
public override function getEventSchema():SongEventSchema
|
||||
{
|
||||
return [
|
||||
return new SongEventSchema([
|
||||
{
|
||||
name: 'target',
|
||||
title: 'Target',
|
||||
|
@ -108,6 +108,6 @@ class PlayAnimationSongEvent extends SongEvent
|
|||
type: SongEventFieldType.BOOL,
|
||||
defaultValue: false
|
||||
}
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import funkin.data.song.SongData;
|
|||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventData.SongEventFieldType;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||
|
||||
/**
|
||||
* This class represents a handler for configuring camera bop intensity and rate.
|
||||
|
@ -72,7 +72,7 @@ class SetCameraBopSongEvent extends SongEvent
|
|||
*/
|
||||
public override function getEventSchema():SongEventSchema
|
||||
{
|
||||
return [
|
||||
return new SongEventSchema([
|
||||
{
|
||||
name: 'intensity',
|
||||
title: 'Intensity',
|
||||
|
@ -87,6 +87,6 @@ class SetCameraBopSongEvent extends SongEvent
|
|||
step: 1,
|
||||
type: SongEventFieldType.INTEGER,
|
||||
}
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package funkin.play.event;
|
||||
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
|
||||
/**
|
||||
* This class represents a handler for a type of song event.
|
||||
|
|
|
@ -8,8 +8,8 @@ import funkin.data.song.SongData;
|
|||
import funkin.data.song.SongData.SongEventData;
|
||||
// Data from the event schema
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventData.SongEventFieldType;
|
||||
import funkin.data.event.SongEventData.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.event.SongEventSchema.SongEventFieldType;
|
||||
|
||||
/**
|
||||
* This class represents a handler for camera zoom events.
|
||||
|
@ -100,7 +100,7 @@ class ZoomCameraSongEvent extends SongEvent
|
|||
*/
|
||||
public override function getEventSchema():SongEventSchema
|
||||
{
|
||||
return [
|
||||
return new SongEventSchema([
|
||||
{
|
||||
name: 'zoom',
|
||||
title: 'Zoom Level',
|
||||
|
@ -146,6 +146,6 @@ class ZoomCameraSongEvent extends SongEvent
|
|||
'Elastic In/Out' => 'elasticInOut',
|
||||
]
|
||||
}
|
||||
];
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,25 +80,11 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler
|
|||
if (FlxG.keys.justPressed.F5) debug_refreshModules();
|
||||
}
|
||||
|
||||
function handleQuickWatch():Void
|
||||
{
|
||||
// Display Conductor info in the watch window.
|
||||
FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition);
|
||||
FlxG.watch.addQuick("songPositionNoOffset", Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
|
||||
FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0);
|
||||
FlxG.watch.addQuick("bpm", Conductor.instance.bpm);
|
||||
FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentBeatTime);
|
||||
FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime);
|
||||
FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime);
|
||||
}
|
||||
|
||||
override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
handleControls();
|
||||
handleFunctionControls();
|
||||
handleQuickWatch();
|
||||
|
||||
dispatchEvent(new UpdateScriptEvent(elapsed));
|
||||
}
|
||||
|
|
|
@ -149,7 +149,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');
|
||||
|
@ -491,17 +491,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.
|
||||
|
@ -1884,6 +1884,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();
|
||||
|
@ -2474,23 +2475,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));
|
||||
|
||||
|
@ -2652,7 +2637,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);
|
||||
|
@ -2661,6 +2646,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.
|
||||
|
@ -2850,6 +2871,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
playMetronomeTick(Conductor.instance.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;
|
||||
}
|
||||
|
||||
|
@ -3053,6 +3078,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
|
||||
{
|
||||
|
@ -3509,6 +3536,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,
|
||||
|
@ -4092,14 +4121,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));
|
||||
|
||||
|
@ -4137,13 +4166,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
|
||||
{
|
||||
|
@ -4164,11 +4232,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;
|
||||
|
||||
|
@ -4184,11 +4252,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();
|
||||
}
|
||||
|
@ -4481,7 +4549,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
|
||||
|
@ -4786,11 +4854,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
#end
|
||||
}
|
||||
|
||||
override function handleQuickWatch():Void
|
||||
function handleQuickWatch():Void
|
||||
{
|
||||
super.handleQuickWatch();
|
||||
|
||||
FlxG.watch.addQuick('musicTime', audioInstTrack?.time);
|
||||
FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0);
|
||||
|
||||
FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels);
|
||||
FlxG.watch.addQuick('playheadPosInPixels', playheadPositionInPixels);
|
||||
|
@ -5545,6 +5611,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
|||
|
||||
cleanupAutoSave();
|
||||
|
||||
this.closeAllMenus();
|
||||
|
||||
// Hide the mouse cursor on other states.
|
||||
Cursor.hide();
|
||||
|
||||
|
|
|
@ -33,6 +33,32 @@ 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;
|
||||
|
||||
// This code is here to parse event data that's not built as a struct for some reason.
|
||||
// TODO: Clean this up or get rid of it.
|
||||
var eventSchema = eventSelected.getSchema();
|
||||
var defaultKey = null;
|
||||
if (eventSchema == null)
|
||||
{
|
||||
trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultKey = eventSchema.getFirstField()?.name;
|
||||
}
|
||||
var eventData = eventSelected.valueAsStruct(defaultKey);
|
||||
|
||||
state.eventDataToPlace = eventData;
|
||||
|
||||
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT);
|
||||
}
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
state.notePreviewDirty = true;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,32 @@ 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;
|
||||
|
||||
// This code is here to parse event data that's not built as a struct for some reason.
|
||||
// TODO: Clean this up or get rid of it.
|
||||
var eventSchema = eventSelected.getSchema();
|
||||
var defaultKey = null;
|
||||
if (eventSchema == null)
|
||||
{
|
||||
trace('[WARNING] Event schema not found for event ${eventSelected.event}.');
|
||||
}
|
||||
else
|
||||
{
|
||||
defaultKey = eventSchema.getFirstField()?.name;
|
||||
}
|
||||
var eventData = eventSelected.valueAsStruct(defaultKey);
|
||||
|
||||
state.eventDataToPlace = eventData;
|
||||
|
||||
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT);
|
||||
}
|
||||
|
||||
state.noteDisplayDirty = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import funkin.data.event.SongEventData.SongEventParser;
|
||||
import funkin.data.event.SongEventRegistry;
|
||||
import flixel.graphics.frames.FlxAtlasFrames;
|
||||
import openfl.display.BitmapData;
|
||||
import openfl.utils.Assets;
|
||||
|
@ -79,7 +79,7 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
}
|
||||
|
||||
// Push all the other events as frames.
|
||||
for (eventName in SongEventParser.listEventIds())
|
||||
for (eventName in SongEventRegistry.listEventIds())
|
||||
{
|
||||
var exists:Bool = Assets.exists(Paths.image('ui/chart-editor/events/$eventName'));
|
||||
if (!exists) continue; // No graphic for this event.
|
||||
|
@ -105,7 +105,7 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
|
||||
function buildAnimations():Void
|
||||
{
|
||||
var eventNames:Array<String> = [DEFAULT_EVENT].concat(SongEventParser.listEventIds());
|
||||
var eventNames:Array<String> = [DEFAULT_EVENT].concat(SongEventRegistry.listEventIds());
|
||||
for (eventName in eventNames)
|
||||
{
|
||||
this.animation.addByPrefix(eventName, '${eventName}0', 24, false);
|
||||
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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???
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import haxe.ui.containers.TreeView;
|
|||
import haxe.ui.containers.TreeViewNode;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.event.SongEvent;
|
||||
import funkin.data.event.SongEventData;
|
||||
import funkin.data.event.SongEventSchema;
|
||||
import funkin.data.song.SongData.SongTimeChange;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.play.character.CharacterData;
|
||||
|
@ -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
|
||||
|
@ -586,8 +445,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);
|
||||
|
@ -597,7 +454,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>
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.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.SongEventRegistry;
|
||||
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> = SongEventRegistry.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 = SongEventRegistry.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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
35
source/funkin/util/plugins/EvacuateDebugPlugin.hx
Normal file
35
source/funkin/util/plugins/EvacuateDebugPlugin.hx
Normal 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();
|
||||
}
|
||||
}
|
5
source/funkin/util/plugins/README.md
Normal file
5
source/funkin/util/plugins/README.md
Normal 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
|
38
source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx
Normal file
38
source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx
Normal 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();
|
||||
}
|
||||
}
|
38
source/funkin/util/plugins/WatchPlugin.hx
Normal file
38
source/funkin/util/plugins/WatchPlugin.hx
Normal 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.instance.songPosition);
|
||||
FlxG.watch.addQuick("songPositionNoOffset", Conductor.instance.songPosition + Conductor.instance.instrumentalOffset);
|
||||
FlxG.watch.addQuick("musicTime", FlxG.sound?.music?.time ?? 0.0);
|
||||
FlxG.watch.addQuick("bpm", Conductor.instance.bpm);
|
||||
FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime);
|
||||
FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime);
|
||||
FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime);
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue