diff --git a/assets b/assets index c1899ffbef..9647e20caa 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit c1899ffbefb9a7c98b030c75a33623431d7ea6ba +Subproject commit 9647e20caa1b74a12d81d290b77aaba8533ce8a2 diff --git a/source/funkin/data/notes/SongNoteSchema.hx b/source/funkin/data/notes/SongNoteSchema.hx new file mode 100644 index 0000000000..8e97d65f53 --- /dev/null +++ b/source/funkin/data/notes/SongNoteSchema.hx @@ -0,0 +1,151 @@ +package funkin.data.notes; + +@:forward(name, title, type, min, max, step, precision, keys, defaultValue, iterator) +abstract SongNoteSchema(SongNoteSchemaRaw) +{ + public function new(?fields:Array) + { + this = fields; + } + + @:arrayAccess + public function getByName(name:String):SongNoteSchemaField + { + for (field in this) + { + if (field.name == name) return field; + } + + return null; + } + + public function getFirstField():SongNoteSchemaField + { + return this[0]; + } + + @:arrayAccess + public inline function get(index:Int):SongNoteSchemaField + { + return this[index]; + } + + @:arrayAccess + public inline function set(index:Int, value:SongNoteSchemaField):SongNoteSchemaField + { + return this[index] = value; + } + + public function stringifyFieldValue(name:String, value:Dynamic):String + { + var field:SongNoteSchemaField = getByName(name); + if (field == null) return 'Unknown'; + + switch (field.type) + { + case SongNoteFieldType.STRING: + return Std.string(value); + case SongNoteFieldType.INTEGER | SongNoteFieldType.FLOAT | SongNoteFieldType.BOOL: + var returnValue:String = Std.string(value); + return returnValue; + case SongNoteFieldType.ENUM: + var valueString:String = Std.string(value); + for (key in field.keys.keys()) + { + // Comparing these values as strings because comparing Dynamic variables is jank. + if (Std.string(field.keys.get(key)) == valueString) return key; + } + return valueString; + default: + return 'Unknown'; + } + } +} + +typedef SongNoteSchemaRaw = Array; + +typedef SongNoteSchemaField = +{ + /** + * 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:SongNoteFieldType, + + /** + * 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, + + /** + * Used for INTEGER and FLOAT values. + * The amount of decimal places. + * @default `0` + */ + ?precision:Int, + + /** + * Used only for ENUM values. + * The key is the display name and the value is the actual value. + */ + ?keys:Map, + + /** + * An optional default value for the field. + */ + ?defaultValue:Dynamic, +} + +enum abstract SongNoteFieldType(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"; +} diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 074ed0b440..a68aba64c8 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -3,7 +3,9 @@ package funkin.data.song; import funkin.data.event.SongEventRegistry; import funkin.play.event.SongEvent; import funkin.data.event.SongEventSchema; +import funkin.data.notes.SongNoteSchema; import funkin.data.song.SongRegistry; +import funkin.play.notes.notekind.NoteKindManager; import thx.semver.Version; import funkin.util.tools.ICloneable; @@ -984,17 +986,18 @@ class SongNoteDataRaw implements ICloneable } @:alias("p") - @:default([]) @:optional - public var params:Array; + @:jcustomparse(funkin.data.DataParse.dynamicValue) + @:jcustomwrite(funkin.data.DataWrite.dynamicValue) + public var params:Dynamic = null; - public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array) + public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Dynamic) { this.time = time; this.data = data; this.length = length; this.kind = kind; - this.params = params ?? []; + this.params = params; } /** @@ -1003,7 +1006,7 @@ class SongNoteDataRaw implements ICloneable * * 0 = left, 1 = down, 2 = up, 3 = right */ - public inline function getDirection(strumlineSize:Int = 4):Int + public function getDirection(strumlineSize:Int = 4):Int { return this.data % strumlineSize; } @@ -1089,25 +1092,109 @@ class SongNoteDataRaw implements ICloneable _stepLength = null; } - public function cloneParams():Array + public function clone():SongNoteDataRaw + { + return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, this.params); + } + + public function toString():String { - var params:Array = []; - for (param in this.params) + return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}' + + (this.kind != '' ? ' [kind: ${this.kind}])' : ')'); + } + + public function paramsAsStruct(?defaultKey:String = "key"):Dynamic + { + if (this.params == null) return {}; + + if (Reflect.isObject(this.params)) { - params.push(param.clone()); + // We enter this case if the params are a struct. + return cast this.params; + } + else + { + var result:haxe.DynamicAccess = {}; + result.set(defaultKey, this.params); + return cast result; } - return params; } - public function clone():SongNoteDataRaw + public function getSchema():Null { - return new SongNoteDataRaw(this.time, this.data, this.length, this.kind, cloneParams()); + return NoteKindManager.getSchema(this.kind); } - public function toString():String + public function getDynamic(key:String):Null { - return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}' - + (this.kind != '' ? ' [kind: ${this.kind}])' : ')'); + return this.params == null ? null : Reflect.field(this.params, key); + } + + public function getBool(key:String):Null + { + return this.params == null ? null : cast Reflect.field(this.params, key); + } + + public function getInt(key:String):Null + { + if (this.params == null) return null; + var result = Reflect.field(this.params, key); + if (result == null) return null; + if (Std.isOfType(result, Int)) return result; + if (Std.isOfType(result, String)) return Std.parseInt(cast result); + return cast result; + } + + public function getFloat(key:String):Null + { + if (this.params == null) return null; + var result = Reflect.field(this.params, key); + if (result == null) return null; + if (Std.isOfType(result, Float)) return result; + if (Std.isOfType(result, String)) return Std.parseFloat(cast result); + return cast result; + } + + public function getString(key:String):String + { + return this.params == null ? null : cast Reflect.field(this.params, key); + } + + public function getArray(key:String):Array + { + return this.params == null ? null : cast Reflect.field(this.params, key); + } + + public function getBoolArray(key:String):Array + { + return this.params == null ? null : cast Reflect.field(this.params, key); + } + + public function buildTooltip():Null + { + var noteSchema = getSchema(); + + if (noteSchema == null) return null; + + var result = '${this.kind}'; + + var defaultKey = noteSchema.getFirstField()?.name; + var paramsStruct:haxe.DynamicAccess = paramsAsStruct(defaultKey); + + for (pair in paramsStruct.keyValueIterator()) + { + var key = pair.key; + var value = pair.value; + + var title = noteSchema.getByName(key)?.title ?? 'UnknownField'; + + // if (noteSchema.stringifyFieldValue(key, value) != null) trace(noteSchema.stringifyFieldValue(key, value)); + var valueStr = noteSchema.stringifyFieldValue(key, value) ?? 'UnknownValue'; + + result += '\n- ${title}: ${valueStr}'; + } + + return result; } } @@ -1117,7 +1204,7 @@ class SongNoteDataRaw implements ICloneable @:forward abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw { - public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Array) + public function new(time:Float, data:Int, length:Float = 0, kind:String = '', ?params:Dynamic) { this = new SongNoteDataRaw(time, data, length, kind, params); } diff --git a/source/funkin/play/notes/NoteSprite.hx b/source/funkin/play/notes/NoteSprite.hx index e8cacaa4d6..d66a2f5957 100644 --- a/source/funkin/play/notes/NoteSprite.hx +++ b/source/funkin/play/notes/NoteSprite.hx @@ -67,16 +67,16 @@ class NoteSprite extends FunkinSprite } /** - * An array of custom parameters for this note + * Custom parameters for this note */ - public var params(get, set):Array; + public var params(get, set):Dynamic; - function get_params():Array + function get_params():Dynamic { return this.noteData?.params ?? []; } - function set_params(value:Array):Array + function set_params(value:Dynamic):Dynamic { if (this.noteData == null) return value; return this.noteData.params = value; @@ -175,14 +175,7 @@ class NoteSprite extends FunkinSprite */ public function getParam(name:String):Null { - for (param in params) - { - if (param.name == name) - { - return param.value; - } - } - return null; + return this.noteData?.getDynamic(name); } #if FLX_DEBUG diff --git a/source/funkin/play/notes/notekind/NoteKind.hx b/source/funkin/play/notes/notekind/NoteKind.hx index c1c6e815aa..829bb16b8f 100644 --- a/source/funkin/play/notes/notekind/NoteKind.hx +++ b/source/funkin/play/notes/notekind/NoteKind.hx @@ -1,5 +1,6 @@ package funkin.play.notes.notekind; +import funkin.data.notes.SongNoteSchema; import funkin.modding.IScriptedClass.INoteScriptedClass; import funkin.modding.events.ScriptEvent; import flixel.math.FlxMath; @@ -15,9 +16,9 @@ class NoteKind implements INoteScriptedClass public var noteKind:String; /** - * Description used in chart editor + * Title used in chart editor */ - public var description:String; + public var title:String; /** * Custom note style @@ -25,16 +26,16 @@ class NoteKind implements INoteScriptedClass public var noteStyleId:Null; /** - * Custom parameters for the chart editor + * Schema for the chart editor */ - public var params:Array; + public var schema:Null; - public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array) + public function new(noteKind:String, title:String = "", ?noteStyleId:String, ?schema:SongNoteSchema) { this.noteKind = noteKind; - this.description = description; + this.title = title; this.noteStyleId = noteStyleId; - this.params = params ?? []; + this.schema = schema; } public function toString():String @@ -68,52 +69,3 @@ class NoteKind implements INoteScriptedClass public function onNoteMiss(event:NoteScriptEvent):Void {} } - -/** - * Abstract for setting the type of the `NoteKindParam` - * This was supposed to be an enum but polymod kept being annoying - */ -abstract NoteKindParamType(String) from String to String -{ - public static final STRING:String = 'String'; - - public static final INT:String = 'Int'; - - public static final FLOAT:String = 'Float'; -} - -typedef NoteKindParamData = -{ - /** - * If `min` is null, there is no minimum - */ - ?min:Null, - - /** - * If `max` is null, there is no maximum - */ - ?max:Null, - - /** - * If `step` is null, it will use 1.0 - */ - ?step:Null, - - /** - * If `precision` is null, there will be 0 decimal places - */ - ?precision:Null, - - ?defaultValue:Dynamic -} - -/** - * Typedef for creating custom parameters in the chart editor - */ -typedef NoteKindParam = -{ - name:String, - description:String, - type:NoteKindParamType, - ?data:NoteKindParamData -} diff --git a/source/funkin/play/notes/notekind/NoteKindManager.hx b/source/funkin/play/notes/notekind/NoteKindManager.hx index e17e103d19..ce48338435 100644 --- a/source/funkin/play/notes/notekind/NoteKindManager.hx +++ b/source/funkin/play/notes/notekind/NoteKindManager.hx @@ -3,10 +3,10 @@ package funkin.play.notes.notekind; import funkin.modding.events.ScriptEventDispatcher; import funkin.modding.events.ScriptEvent; import funkin.ui.debug.charting.util.ChartEditorDropdowns; +import funkin.data.notes.SongNoteSchema; import funkin.data.notestyle.NoteStyleRegistry; import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.notekind.ScriptedNoteKind; -import funkin.play.notes.notekind.NoteKind.NoteKindParam; class NoteKindManager { @@ -25,7 +25,7 @@ class NoteKindManager var script:NoteKind = ScriptedNoteKind.init(scriptedClass, "unknown"); trace(' Initialized scripted note kind: ${script.noteKind}'); noteKinds.set(script.noteKind, script); - ChartEditorDropdowns.NOTE_KINDS.set(script.noteKind, script.description); + ChartEditorDropdowns.NOTE_KINDS.set(script.noteKind, script.title); } catch (e) { @@ -104,18 +104,8 @@ class NoteKindManager return noteStyleId; } - /** - * Retrive custom params of the given note kind - * @param noteKind Name of the note kind - * @return Array - */ - public static function getParams(noteKind:Null):Array + public static function getSchema(noteKind:Null):Null { - if (noteKind == null) - { - return []; - } - - return noteKinds.get(noteKind)?.params ?? []; + return noteKinds.get(noteKind)?.schema; } } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 44c14be06e..d157c25bb5 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -556,9 +556,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var noteKindToPlace:Null = null; /** - * The note params to use for notes being placed in the chart. Defaults to `[]`. + * The note params to use for notes being placed in the chart. Defaults to `{}`. */ - var noteParamsToPlace:Array = []; + var noteParamsToPlace:DynamicAccess = {}; /** * The event type to use for events being placed in the chart. Defaults to `''`. @@ -4757,8 +4757,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState else { // Create a note and place it in the chart. - var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace, - ChartEditorState.cloneNoteParams(noteParamsToPlace)); + var newNoteData:SongNoteData = new SongNoteData(cursorSnappedMs, cursorColumn, 0, noteKindToPlace, Reflect.copy(noteParamsToPlace)); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); @@ -4918,7 +4917,7 @@ 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, noteKindToPlace, - ChartEditorState.cloneNoteParams(noteParamsToPlace)); + Reflect.copy(noteParamsToPlace)); if (cursorColumn != noteData.data || noteKindToPlace != noteData.kind || noteParamsToPlace != noteData.params) { @@ -5193,7 +5192,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (notesAtPos.length == 0 && !removeNoteInstead) { trace('Placing note. ${column}'); - var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace, ChartEditorState.cloneNoteParams(noteParamsToPlace)); + var newNoteData:SongNoteData = new SongNoteData(playheadPosSnappedMs, column, 0, noteKindToPlace, Reflect.copy(noteParamsToPlace)); performCommand(new AddNotesCommand([newNoteData], FlxG.keys.pressed.CONTROL)); currentLiveInputPlaceNoteData[column] = newNoteData; } @@ -6484,16 +6483,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } return input; } - - public static function cloneNoteParams(paramsToClone:Array):Array - { - var params:Array = []; - for (param in paramsToClone) - { - params.push(param.clone()); - } - return params; - } } /** diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx index 423295f1ab..b07283b238 100644 --- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx @@ -65,6 +65,10 @@ class SelectItemsCommand implements ChartEditorCommand var noteSelected = this.notes[0]; state.noteKindToPlace = noteSelected.kind; + var noteSchema = noteSelected.getSchema(); + var defaultKey = noteSchema?.getFirstField()?.name; + var noteParams = noteSelected.paramsAsStruct(defaultKey); + state.noteParamsToPlace = noteParams; // This code is here to parse note data that's not built as a struct for some reason. state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTE_DATA_LAYOUT); diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 661c44d85a..589b1bd061 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -76,6 +76,11 @@ class SetItemSelectionCommand implements ChartEditorCommand state.noteKindToPlace = noteSelected.kind; + var noteSchema = noteSelected.getSchema(); + var defaultKey = noteSchema?.getFirstField()?.name; + var noteParams = noteSelected.paramsAsStruct(defaultKey); + state.noteParamsToPlace = noteParams; + state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTE_DATA_LAYOUT); } diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorNoteDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorNoteDataToolbox.hx index 100654a025..10a282a6b9 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorNoteDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorNoteDataToolbox.hx @@ -1,17 +1,20 @@ package funkin.ui.debug.charting.toolboxes; +import haxe.ui.core.Component; import haxe.ui.components.DropDown; import haxe.ui.components.TextField; import haxe.ui.components.Label; import haxe.ui.components.NumberStepper; +import haxe.ui.components.CheckBox; import haxe.ui.containers.Grid; -import haxe.ui.core.Component; +import haxe.ui.containers.Box; +import haxe.ui.containers.VBox; +import haxe.ui.containers.Frame; +import haxe.ui.data.ArrayDataSource; import haxe.ui.events.UIEvent; import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.play.notes.notekind.NoteKindManager; -import funkin.play.notes.notekind.NoteKind.NoteKindParam; -import funkin.play.notes.notekind.NoteKind.NoteKindParamType; -import funkin.data.song.SongData.NoteParamData; +import funkin.data.notes.SongNoteSchema; /** * The toolbox which allows modifying information like Note Kind. @@ -23,19 +26,11 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox // 100 is the height used in note-data.xml static final DIALOG_HEIGHT:Int = 100; - // toolboxNotesGrid.height + 45 - // this is what i found out by printing this.height and grid.height - // and then seeing that this.height is 100 and grid.height is 55 - static final HEIGHT_OFFSET:Int = 45; - - // minimizing creates a gray bar the bottom, which would obscure the components, - // which is why we use an extra offset of 20 - static final MINIMIZE_FIX:Int = 20; - - var toolboxNotesGrid:Grid; + var toolboxNoteParamsGrid:Grid; var toolboxNotesNoteKind:DropDown; var toolboxNotesCustomKind:TextField; - var toolboxNotesParams:Array = []; + + var previousNoteKind:String = ''; var _initializing:Bool = true; @@ -76,7 +71,7 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox toolboxNotesCustomKind.value = chartEditorState.noteKindToPlace; } - createNoteKindParams(noteKind); + buildNoteParamsFormFromSchema(toolboxNoteParamsGrid); if (!_initializing && chartEditorState.currentNoteSelection.length > 0) { @@ -84,7 +79,7 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox { // Edit the note data of any selected notes. note.kind = chartEditorState.noteKindToPlace; - note.params = ChartEditorState.cloneNoteParams(chartEditorState.noteParamsToPlace); + note.params = Reflect.copy(chartEditorState.noteParamsToPlace); // update note sprites for (noteSprite in chartEditorState.renderedNotes.members) @@ -140,7 +135,45 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox toolboxNotesNoteKind.value = ChartEditorDropdowns.lookupNoteKind(chartEditorState.noteKindToPlace); toolboxNotesCustomKind.value = chartEditorState.noteKindToPlace; - createNoteKindParams(chartEditorState.noteKindToPlace); + buildNoteParamsFormFromSchema(toolboxNoteParamsGrid); + + if (chartEditorState.noteParamsToPlace == null) + { + return; + } + + for (pair in chartEditorState.noteParamsToPlace.keyValueIterator()) + { + var fieldId:String = pair.key; + var value:Null = pair.value; + + var field:Component = toolboxNoteParamsGrid.findComponent(fieldId); + + if (field == null) + { + throw 'ChartEditorToolboxHandler.refresh() - Field "${fieldId}" does not exist in the event data form for kind ${previousNoteKind}.'; + } + 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 showCustom():Void @@ -155,138 +188,136 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox toolboxNotesCustomKind.hidden = true; } - function createNoteKindParams(noteKind:Null):Void + function buildNoteParamsFormFromSchema(target:Box):Void { - clearNoteKindParams(); - - var setParamsToPlace:Bool = false; - if (!_initializing) + if (chartEditorState.noteKindToPlace == previousNoteKind) { - for (note in chartEditorState.currentNoteSelection) - { - if (note.kind == chartEditorState.noteKindToPlace) - { - chartEditorState.noteParamsToPlace = ChartEditorState.cloneNoteParams(note.params); - setParamsToPlace = true; - break; - } - } + return; } - var noteKindParams:Array = NoteKindManager.getParams(noteKind); + trace('Building note params form from schema for note kind: ${chartEditorState.noteKindToPlace}'); + // trace(schema); + + previousNoteKind = chartEditorState.noteKindToPlace ?? ''; + + // Clear the frame. + target.removeAllComponents(); + + chartEditorState.noteParamsToPlace = {}; - for (i in 0...noteKindParams.length) + var schema:Null = NoteKindManager.getSchema(chartEditorState.noteKindToPlace); + + if (schema == null) { - var param:NoteKindParam = noteKindParams[i]; + return; + } - var paramLabel:Label = new Label(); - paramLabel.value = param.description; - paramLabel.verticalAlign = "center"; - paramLabel.horizontalAlign = "right"; + for (field in schema) + { + if (field == null) continue; - var paramComponent:Component = null; + // Add a label for the data field. + var label:Label = new Label(); + label.text = field.title; + label.verticalAlign = "center"; + target.addComponent(label); - switch (param.type) + // Add an input field for the data field. + var input:Component; + switch (field.type) { - case NoteKindParamType.INT | NoteKindParamType.FLOAT: - var paramStepper:NumberStepper = new NumberStepper(); - paramStepper.value = (setParamsToPlace ? chartEditorState.noteParamsToPlace[i].value : param.data?.defaultValue) ?? 0.0; - paramStepper.percentWidth = 100; - paramStepper.step = param.data?.step ?? 1.0; - - // this check should be unnecessary but for some reason - // even when these are null it will set it to 0 - if (param.data?.min != null) - { - paramStepper.min = param.data.min; - } - if (param.data?.max != null) - { - paramStepper.max = param.data.max; - } - if (param.data?.precision != null) + case INTEGER | FLOAT: + 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; + numberStepper.precision = field.precision ?? 0; + 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.dropdownSize = 10; + dropDown.dropdownWidth = 300; + dropDown.searchable = true; + 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()) { - paramStepper.precision = param.data.precision; + var optionValue:Null = field.keys.get(optionName); + // trace('$optionName : $optionValue'); + dropDown.dataSource.add({value: optionValue, text: optionName}); } - paramComponent = paramStepper; - case NoteKindParamType.STRING: - var paramTextField:TextField = new TextField(); - paramTextField.value = (setParamsToPlace ? chartEditorState.noteParamsToPlace[i].value : param.data?.defaultValue) ?? ''; - paramTextField.percentWidth = 100; - paramComponent = paramTextField; + dropDown.value = field.defaultValue; + + // TODO: Add an option to customize sort. + dropDown.dataSource.sort('text', ASCENDING); + + 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; } - if (paramComponent == null) - { - continue; - } + target.addComponent(input); - paramComponent.onChange = function(event:UIEvent) { - chartEditorState.noteParamsToPlace[i].value = paramComponent.value; + // 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; + } + else if (field.type == BOOL) + { + var chk:CheckBox = cast event.target; + value = cast(chk.selected, Null); // Need to cast to nullable bool or the compiler will get mad. + } - for (note in chartEditorState.currentNoteSelection) + trace('ChartEditorToolboxHandler.buildNoteParamsFormFromSchema() - ${event.target.id} = ${value}'); + + // Edit the event data to place. + if (value == null) { - if (note.params.length != noteKindParams.length) - { - break; - } + chartEditorState.noteParamsToPlace.remove(event.target.id); + } + else + { + chartEditorState.noteParamsToPlace.set(event.target.id, value); + } - if (note.params[i].name == param.name) + // Edit the note params of any selected notes. + if (!_initializing && chartEditorState.currentNoteSelection.length > 0) + { + for (note in chartEditorState.currentNoteSelection) { - note.params[i].value = paramComponent.value; + note.kind = chartEditorState.noteKindToPlace; + note.params = Reflect.copy(chartEditorState.noteParamsToPlace); } + chartEditorState.saveDataDirty = true; + chartEditorState.noteDisplayDirty = true; + chartEditorState.notePreviewDirty = true; + chartEditorState.noteTooltipsDirty = true; } } - - addNoteKindParam(paramLabel, paramComponent); - } - - if (!setParamsToPlace) - { - var noteParamData:Array = []; - for (i in 0...noteKindParams.length) - { - noteParamData.push(new NoteParamData(noteKindParams[i].name, toolboxNotesParams[i].component.value)); - } - chartEditorState.noteParamsToPlace = noteParamData; - } - } - - function addNoteKindParam(label:Label, component:Component):Void - { - toolboxNotesParams.push({label: label, component: component}); - toolboxNotesGrid.addComponent(label); - toolboxNotesGrid.addComponent(component); - - this.height = Math.max(DIALOG_HEIGHT, DIALOG_HEIGHT - 30 + toolboxNotesParams.length * 30); - } - - function clearNoteKindParams():Void - { - for (param in toolboxNotesParams) - { - toolboxNotesGrid.removeComponent(param.component); - toolboxNotesGrid.removeComponent(param.label); - } - toolboxNotesParams = []; - this.height = DIALOG_HEIGHT; - } - - override function update(elapsed:Float):Void - { - super.update(elapsed); - - // current dialog is minimized, dont change the height - if (this.minimized) - { - return; - } - - var heightToSet:Int = Std.int(Math.max(DIALOG_HEIGHT, (toolboxNotesGrid?.height ?? 50.0) + HEIGHT_OFFSET)) + MINIMIZE_FIX; - if (this.height != heightToSet) - { - this.height = heightToSet; } } @@ -295,9 +326,3 @@ class ChartEditorNoteDataToolbox extends ChartEditorBaseToolbox return new ChartEditorNoteDataToolbox(chartEditorState); } } - -typedef ToolboxNoteKindParam = -{ - var label:Label; - var component:Component; -}