diff --git a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx index 3e1c41ac5..afc270c5e 100644 --- a/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx +++ b/source/funkin/ui/debug/charting/toolboxes/ChartEditorEventDataToolbox.hx @@ -15,6 +15,7 @@ import haxe.ui.containers.Frame; import haxe.ui.events.UIEvent; import haxe.ui.data.ArrayDataSource; import haxe.ui.containers.Grid; +import funkin.play.event.SongEvent; /** * The toolbox which allows modifying information like Song Title, Scroll Speed, Characters/Stages, and starting BPM. @@ -170,13 +171,51 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox function buildEventDataFormFromSchema(target:Box, schema:SongEventSchema, eventKind:String):Void { trace('Building event data form from schema for event kind: ${eventKind}'); - // trace(schema); - lastEventKind = eventKind ?? 'unknown'; // Clear the frame. target.removeAllComponents(); + // Check if we have both ease and easeDir fields in the schema + var hasEaseField = false; + var hasEaseDirField = false; + for (field in schema) + { + if (field != null) + { + if (field.name == 'ease') hasEaseField = true; + if (field.name == 'easeDir') hasEaseDirField = true; + } + } + + // Convert ease data for ALL events (both new and existing) + if (hasEaseField && hasEaseDirField) + { + final originalData:Map = new Map(); + for (key in chartEditorState.eventDataToPlace.keys()) + originalData.set(key, chartEditorState.eventDataToPlace.get(key)); + + final converted:Bool = convertEaseData(chartEditorState.eventDataToPlace); + + for (key => value in originalData) + { + if (key != 'ease' && key != 'easeDir') chartEditorState.eventDataToPlace.set(key, value); + } + + if (converted && !_initializing && chartEditorState.currentEventSelection.length > 0) + { + for (songEvent in chartEditorState.currentEventSelection) + { + songEvent.eventKind = chartEditorState.eventKindToPlace; + songEvent.value = Reflect.copy(chartEditorState.eventDataToPlace); + } + chartEditorState.saveDataDirty = true; + chartEditorState.noteDisplayDirty = true; + chartEditorState.notePreviewDirty = true; + chartEditorState.noteTooltipsDirty = true; + } + } + for (field in schema) { if (field == null) continue; @@ -189,6 +228,7 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox // Add an input field for the data field. var input:Component; + final fieldValue = chartEditorState.eventDataToPlace.get(field.name); switch (field.type) { case INTEGER: @@ -197,7 +237,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox numberStepper.step = field.step ?? 1.0; if (field.min != null) numberStepper.min = field.min; if (field.max != null) numberStepper.max = field.max; - if (field.defaultValue != null) numberStepper.value = field.defaultValue; + if (fieldValue != null) numberStepper.value = fieldValue; + else if (field.defaultValue != null) numberStepper.value = field.defaultValue; input = numberStepper; case FLOAT: var numberStepper:NumberStepper = new NumberStepper(); @@ -205,12 +246,14 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox 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; + if (fieldValue != null) numberStepper.value = fieldValue; + else 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; + if (fieldValue != null) checkBox.selected = fieldValue; + else if (field.defaultValue != null) checkBox.selected = field.defaultValue; input = checkBox; case ENUM: var dropDown:DropDown = new DropDown(); @@ -224,15 +267,13 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox if (field.keys == null) throw 'Field "${field.name}" is of Enum type but has no keys.'; // Add entries to the dropdown. - for (optionName in field.keys.keys()) { var optionValue:Null = field.keys.get(optionName); - // trace('$optionName : $optionValue'); dropDown.dataSource.add({value: optionValue, text: optionName}); } - dropDown.value = field.defaultValue; + dropDown.value = fieldValue ?? field.defaultValue; // TODO: Add an option to customize sort. dropDown.dataSource.sort('text', ASCENDING); @@ -241,7 +282,8 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox case STRING: input = new TextField(); input.id = field.name; - if (field.defaultValue != null) input.text = field.defaultValue; + if (fieldValue != null) input.text = fieldValue; + else 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(); @@ -264,49 +306,204 @@ class ChartEditorEventDataToolbox extends ChartEditorBaseToolbox target.addComponent(inputBox); - // Update the value of the event data. - input.onChange = function(event:UIEvent) { - var value = event.target.value; - if (field.type == ENUM) - { - var drp:DropDown = cast event.target; - value = drp.selectedItem?.value ?? field.defaultValue; - } - 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. - } - - 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 (songEvent in chartEditorState.currentEventSelection) + // Special handling for ease field to automatically detect and separate direction + // No, im not gonna kill myself + if (field.name == 'ease' && hasEaseDirField) + { + input.onChange = function(event:UIEvent) { + var value = event.target.value; + if (field.type == ENUM) { - songEvent.eventKind = chartEditorState.eventKindToPlace; - songEvent.value = Reflect.copy(chartEditorState.eventDataToPlace); + var drp:DropDown = cast event.target; + value = drp.selectedItem?.value ?? field.defaultValue; + } + + trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${value}'); + + final currentData = new Map(); + for (key in chartEditorState.eventDataToPlace.keys()) + currentData.set(key, chartEditorState.eventDataToPlace.get(key)); + + // Check if ease value contains direction and split it + if (value != null && Std.isOfType(value, String)) + { + var converted = convertEaseDataForField(value, target); + if (converted.converted) + { + // Update the display to show base value + input.pauseEvent(UIEvent.CHANGE, true); + switch (input) + { + case Std.isOfType(_, DropDown) => true: + var dropDown:DropDown = cast input; + dropDown.value = converted.baseEase; + case Std.isOfType(_, TextField) => true: + var textField:TextField = cast input; + textField.text = converted.baseEase; + default: + } + input.resumeEvent(UIEvent.CHANGE, true, true); + + for (key => val in currentData) + { + if (key != 'ease' && key != 'easeDir') chartEditorState.eventDataToPlace.set(key, val); + } + chartEditorState.eventDataToPlace.set('ease', converted.baseEase); + chartEditorState.eventDataToPlace.set('easeDir', converted.easeDir); + } + else + { + for (key => val in currentData) + { + if (key != 'ease') chartEditorState.eventDataToPlace.set(key, val); + } + chartEditorState.eventDataToPlace.set(event.target.id, value); + } + } + else + { + for (key => val in currentData) + { + if (key != 'ease') chartEditorState.eventDataToPlace.set(key, val); + } + chartEditorState.eventDataToPlace.set(event.target.id, value); + } + + // Edit the event data of any existing events. + if (!_initializing && chartEditorState.currentEventSelection.length > 0) + { + for (songEvent in chartEditorState.currentEventSelection) + { + songEvent.eventKind = chartEditorState.eventKindToPlace; + songEvent.value = Reflect.copy(chartEditorState.eventDataToPlace); + } + chartEditorState.saveDataDirty = true; + chartEditorState.noteDisplayDirty = true; + chartEditorState.notePreviewDirty = true; + chartEditorState.noteTooltipsDirty = true; + } + }; + } + else + { + // Standard onChange handler for non-ease fields + input.onChange = function(event:UIEvent) { + var value = event.target.value; + if (field.type == ENUM) + { + var drp:DropDown = cast event.target; + value = drp.selectedItem?.value ?? field.defaultValue; + } + else if (field.type == BOOL) + { + var chk:CheckBox = cast event.target; + value = cast(chk.selected, Null); + } + + trace('ChartEditorToolboxHandler.buildEventDataFormFromSchema() - ${event.target.id} = ${value}'); + + // Update 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 (songEvent in chartEditorState.currentEventSelection) + { + songEvent.eventKind = chartEditorState.eventKindToPlace; + songEvent.value = Reflect.copy(chartEditorState.eventDataToPlace); + } + chartEditorState.saveDataDirty = true; + chartEditorState.noteDisplayDirty = true; + chartEditorState.notePreviewDirty = true; + chartEditorState.noteTooltipsDirty = true; } - chartEditorState.saveDataDirty = true; - chartEditorState.noteDisplayDirty = true; - chartEditorState.notePreviewDirty = true; - chartEditorState.noteTooltipsDirty = true; } } } } + /** + * Converts ease data in the event data map by splitting combined ease values + * @return Whether conversion occurred + */ + function convertEaseData(data:haxe.DynamicAccess):Bool + { + if (data == null) return false; + + var easeValue = data.get('ease'); + if (easeValue != null && Std.isOfType(easeValue, String)) + { + var easeString:String = easeValue; + trace('convertEaseData: Processing ease string "${easeString}"'); + + if (SongEvent.EASE_TYPE_DIR_REGEX.match(easeString)) + { + var baseEase = SongEvent.EASE_TYPE_DIR_REGEX.matchedLeft(); + var easeDir = SongEvent.EASE_TYPE_DIR_REGEX.matched(0); + + // Update the event data with separated values + data.set('ease', baseEase); + data.set('easeDir', easeDir); + + trace('Auto-split ease on load: ${easeString} -> ${baseEase} + ${easeDir}'); + return true; + } + else + { + trace('convertEaseData: No direction suffix found in "${easeString}"'); + } + } + else + { + trace('convertEaseData: easeValue is null or not a string: ${easeValue}'); + } + + return false; + } + + /** + * Converts ease data for a specific field value and updates the UI + * @return Object with conversion result + */ + function convertEaseDataForField(easeValue:String, target:Box):{converted:Bool, baseEase:String, easeDir:String} + { + if (easeValue != null && SongEvent.EASE_TYPE_DIR_REGEX.match(easeValue)) + { + var baseEase = SongEvent.EASE_TYPE_DIR_REGEX.matchedLeft(); + var easeDir = SongEvent.EASE_TYPE_DIR_REGEX.matched(0); + + // Find and update the easeDir field if it exists + var easeDirField:Component = target.findComponent('easeDir', Component); + if (easeDirField != null) + { + easeDirField.pauseEvent(UIEvent.CHANGE, true); + switch (easeDirField) + { + case Std.isOfType(_, DropDown) => true: + var easeDirDropDown:DropDown = cast easeDirField; + easeDirDropDown.value = easeDir; + case Std.isOfType(_, TextField) => true: + var easeDirTextField:TextField = cast easeDirField; + easeDirTextField.text = easeDir; + default: + } + easeDirField.resumeEvent(UIEvent.CHANGE, true, true); + + trace('ChartEditorToolboxHandler - Auto-split ease: ${easeValue} -> ${baseEase} + ${easeDir}'); + return {converted: true, baseEase: baseEase, easeDir: easeDir}; + } + } + return {converted: false, baseEase: easeValue, easeDir: ''}; + } + public static function build(chartEditorState:ChartEditorState):ChartEditorEventDataToolbox { return new ChartEditorEventDataToolbox(chartEditorState);