Work on Upload Vocals dialog

This commit is contained in:
Eric Myllyoja 2022-12-01 17:32:10 -05:00
parent 3f6cbb61d4
commit fbf92acf23
12 changed files with 348 additions and 59 deletions

View file

@ -1,7 +1,6 @@
package funkin.modding.base;
import flixel.addons.display.FlxRuntimeShader;
import polymod.hscript.HScriptedClass;
@:hscriptClass
class ScriptedFlxRuntimeShader extends FlxRuntimeShader implements HScriptedClass {}
class ScriptedFlxRuntimeShader extends flixel.addons.display.FlxRuntimeShader implements HScriptedClass
{
}

View file

@ -1,7 +1,6 @@
package funkin.modding.base;
import flixel.FlxSprite;
import polymod.hscript.HScriptedClass;
@:hscriptClass
class ScriptedFlxSprite extends FlxSprite implements HScriptedClass {}
class ScriptedFlxSprite extends flixel.FlxSprite implements HScriptedClass
{
}

View file

@ -1,7 +1,6 @@
package funkin.modding.base;
import flixel.group.FlxSpriteGroup;
import polymod.hscript.HScriptedClass;
@:hscriptClass
class ScriptedFlxSpriteGroup extends FlxSpriteGroup implements HScriptedClass {}
class ScriptedFlxSpriteGroup extends flixel.group.FlxSpriteGroup implements HScriptedClass
{
}

View file

@ -1,7 +1,6 @@
package funkin.modding.base;
import flixel.FlxState;
import polymod.hscript.HScriptedClass;
@:hscriptClass
class ScriptedFlxState extends FlxState implements HScriptedClass {}
class ScriptedFlxState extends flixel.FlxState implements HScriptedClass
{
}

View file

@ -1,7 +1,6 @@
package funkin.modding.base;
import flixel.FlxSubState;
import polymod.hscript.HScriptedClass;
@:hscriptClass
class ScriptedFlxSubState extends FlxSubState implements HScriptedClass {}
class ScriptedFlxSubState extends flixel.FlxSubState implements HScriptedClass
{
}

View file

@ -1,7 +1,6 @@
package funkin.modding.base;
import flixel.addons.transition.FlxTransitionableState;
import polymod.hscript.HScriptedClass;
@:hscriptClass
class ScriptedFlxTransitionableState extends FlxTransitionableState implements HScriptedClass {}
class ScriptedFlxTransitionableState extends flixel.addons.transition.FlxTransitionableState implements HScriptedClass
{
}

View file

@ -1,7 +1,6 @@
package funkin.modding.base;
import flixel.addons.ui.FlxUIState;
import polymod.hscript.HScriptedClass;
@:hscriptClass
class ScriptedFlxUIState extends FlxUIState implements HScriptedClass {}
class ScriptedFlxUIState extends flixel.addons.ui.FlxUIState implements HScriptedClass
{
}

View file

@ -1,7 +1,6 @@
package funkin.modding.base;
import funkin.MusicBeatState;
import polymod.hscript.HScriptedClass;
@:hscriptClass
class ScriptedMusicBeatState extends MusicBeatState implements HScriptedClass {}
class ScriptedMusicBeatState extends funkin.MusicBeatState implements HScriptedClass
{
}

View file

@ -1,7 +1,6 @@
package funkin.modding.base;
import funkin.MusicBeatSubstate;
import polymod.hscript.HScriptedClass;
@:hscriptClass
class ScriptedMusicBeatSubstate extends MusicBeatSubstate implements HScriptedClass {}
class ScriptedMusicBeatSubstate extends funkin.MusicBeatSubstate implements HScriptedClass
{
}

View file

@ -0,0 +1 @@
import polymod.hscript.HScriptedClass;

View file

@ -1,34 +1,54 @@
package funkin.ui.debug.charting;
import funkin.input.Cursor;
import haxe.ui.containers.Box;
import haxe.ui.containers.dialogs.Dialogs;
import haxe.ui.components.Link;
import flixel.util.FlxTimer;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.character.BaseCharacter;
import haxe.ui.components.Label;
import haxe.ui.events.MouseEvent;
import funkin.play.song.SongData.SongPlayableChar;
import flixel.FlxSprite;
import haxe.ui.containers.dialogs.Dialog;
import haxe.ui.containers.VBox;
import flixel.util.FlxTimer;
import funkin.input.Cursor;
import funkin.play.song.SongData.SongTimeChange;
import haxe.ui.components.Button;
import haxe.ui.components.DropDown;
import haxe.ui.components.Image;
import haxe.ui.components.Link;
import haxe.ui.components.NumberStepper;
import haxe.ui.components.TextField;
import haxe.ui.containers.Box;
import haxe.ui.containers.dialogs.Dialog;
import haxe.ui.containers.dialogs.Dialogs;
import haxe.ui.containers.properties.Property;
import haxe.ui.containers.properties.PropertyGrid;
import haxe.ui.containers.properties.PropertyGroup;
import haxe.ui.containers.VBox;
import haxe.ui.events.UIEvent;
using Lambda;
class ChartEditorDialogHandler
{
static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT = Paths.ui('chart-editor/dialogs/about');
static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT = Paths.ui('chart-editor/dialogs/welcome');
static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT = Paths.ui('chart-editor/dialogs/upload-inst');
static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT = Paths.ui('chart-editor/dialogs/song-metadata');
static final CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT = Paths.ui('chart-editor/dialogs/song-metadata-chargroup');
static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT = Paths.ui('chart-editor/dialogs/upload-vocals');
static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT = Paths.ui('chart-editor/dialogs/upload-vocals-entry');
static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT = Paths.ui('chart-editor/dialogs/user-guide');
/**
*
*/
public static inline function openAboutDialog(state:ChartEditorState):Void
public static inline function openAboutDialog(state:ChartEditorState):Dialog
{
openDialog(state, CHART_EDITOR_DIALOG_ABOUT_LAYOUT, true, true);
return openDialog(state, CHART_EDITOR_DIALOG_ABOUT_LAYOUT, true, true);
}
/**
* Builds and opens a dialog letting the user create a new chart, open a recent chart, or load from a template.
*/
public static function openWelcomeDialog(state:ChartEditorState, closable:Bool = true):Void
public static function openWelcomeDialog(state:ChartEditorState, closable:Bool = true):Dialog
{
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable);
@ -68,13 +88,32 @@ class ChartEditorDialogHandler
linkCreateBasic.onClick = (_event) ->
{
dialog.hideDialog(DialogButton.CANCEL);
openUploadInstDialog(state, false);
};
// Get the list of songs and insert them as links into the "Create From Song" section.
// Create song wizard
var uploadInstDialog = openUploadInstDialog(state, false);
uploadInstDialog.onDialogClosed = (_event) ->
{
state.isHaxeUIDialogOpen = false;
if (_event.button == DialogButton.APPLY)
{
var songMetadataDialog = openSongMetadataDialog(state);
songMetadataDialog.onDialogClosed = (_event) ->
{
state.isHaxeUIDialogOpen = false;
if (_event.button == DialogButton.APPLY)
{
var uploadVocalsDialog = openUploadVocalsDialog(state);
}
};
}
};
}
public static function openUploadInstDialog(state:ChartEditorState, ?closable:Bool = true):Void
// TODO: Get the list of songs and insert them as links into the "Create From Song" section.
return dialog;
}
public static function openUploadInstDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
{
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT, true, closable);
@ -117,6 +156,8 @@ class ChartEditorDialogHandler
};
addDropHandler(onDropFile);
return dialog;
}
static function addDropHandler(handler:String->Void)
@ -135,12 +176,253 @@ class ChartEditorDialogHandler
#end
}
public static function openSongMetadataDialog(state:ChartEditorState):Dialog
{
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
var dialogSongName:TextField = dialog.findComponent('dialogSongName', TextField);
dialogSongName.onChange = (event:UIEvent) ->
{
var valid = event.target.text != null && event.target.text != "";
if (valid)
{
dialogSongName.removeClass('invalid-value');
state.currentSongMetadata.songName = event.target.text;
}
else
{
state.currentSongMetadata.songName = null;
}
};
state.currentSongMetadata.songName = null;
var dialogSongArtist:TextField = dialog.findComponent('dialogSongArtist', TextField);
dialogSongArtist.onChange = (event:UIEvent) ->
{
var valid = event.target.text != null && event.target.text != "";
if (valid)
{
dialogSongArtist.removeClass('invalid-value');
state.currentSongMetadata.artist = event.target.text;
}
else
{
state.currentSongMetadata.artist = null;
}
};
state.currentSongMetadata.artist = null;
var dialogStage:DropDown = dialog.findComponent('dialogStage', DropDown);
dialogStage.onChange = (event:UIEvent) ->
{
var valid = event.data != null && event.data.id != null;
if (event.data.id == null)
return;
state.currentSongMetadata.playData.stage = event.data.id;
};
state.currentSongMetadata.playData.stage = null;
var dialogNoteSkin:DropDown = dialog.findComponent('dialogNoteSkin', DropDown);
dialogNoteSkin.onChange = (event:UIEvent) ->
{
if (event.data.id == null)
return;
state.currentSongMetadata.playData.noteSkin = event.data.id;
};
state.currentSongMetadata.playData.noteSkin = null;
var dialogBPM:NumberStepper = dialog.findComponent('dialogBPM', NumberStepper);
dialogBPM.onChange = (event:UIEvent) ->
{
if (event.value == null)
return;
var timeChanges = state.currentSongMetadata.timeChanges;
if (timeChanges == null || timeChanges.length == 0)
{
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])];
}
else
{
timeChanges[0].bpm = event.value;
}
state.currentSongMetadata.timeChanges = timeChanges;
};
var dialogCharGrid:PropertyGrid = dialog.findComponent('dialogCharGrid', PropertyGrid);
var dialogCharAdd:Button = dialog.findComponent('dialogCharAdd', Button);
dialogCharAdd.onClick = (_event) ->
{
var charGroup:PropertyGroup;
charGroup = buildCharGroup(state, null, () ->
{
dialogCharGrid.removeComponent(charGroup);
});
dialogCharGrid.addComponent(charGroup);
};
// Empty the character list.
state.currentSongMetadata.playData.playableChars = {};
// Add at least one character group with no Remove button.
dialogCharGrid.addComponent(buildCharGroup(state, 'bf', null));
var dialogContinue:Button = dialog.findComponent('dialogContinue', Button);
dialogContinue.onClick = (_event) ->
{
dialog.hideDialog(DialogButton.APPLY);
};
return dialog;
}
static function buildCharGroup(state:ChartEditorState, ?key:String = null, removeFunc:Void->Void):PropertyGroup
{
var groupKey = key;
var getCharData = () ->
{
if (groupKey == null)
groupKey = 'newChar${state.currentSongMetadata.playData.playableChars.keys().count()}';
var result = state.currentSongMetadata.playData.playableChars.get(groupKey);
if (result == null)
{
result = new SongPlayableChar('', 'dad');
state.currentSongMetadata.playData.playableChars.set(groupKey, result);
}
return result;
}
var moveCharGroup = (target:String) ->
{
var charData = getCharData();
state.currentSongMetadata.playData.playableChars.remove(groupKey);
state.currentSongMetadata.playData.playableChars.set(target, charData);
groupKey = target;
}
var removeGroup = () ->
{
state.currentSongMetadata.playData.playableChars.remove(groupKey);
removeFunc();
}
var charData = getCharData();
var charGroup:PropertyGroup = cast state.buildComponent(CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT);
var charGroupPlayer:DropDown = charGroup.findComponent('charGroupPlayer', DropDown);
charGroupPlayer.onChange = (event:UIEvent) ->
{
charGroup.text = event.data.text;
moveCharGroup(event.data.id);
};
if (key == null)
{
// Find the next available player character.
trace(charGroupPlayer.dataSource.data);
}
var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown);
charGroupOpponent.onChange = (event:UIEvent) ->
{
charData.opponent = event.data.id;
};
charGroupOpponent.value = getCharData().opponent;
var charGroupGirlfriend:DropDown = charGroup.findComponent('charGroupGirlfriend', DropDown);
charGroupGirlfriend.onChange = (event:UIEvent) ->
{
charData.girlfriend = event.data.id;
};
charGroupGirlfriend.value = getCharData().girlfriend;
var charGroupRemove:Button = charGroup.findComponent('charGroupRemove', Button);
charGroupRemove.onClick = (_event:MouseEvent) ->
{
removeGroup();
};
if (removeFunc == null)
charGroupRemove.hidden = true;
return charGroup;
}
public static function openUploadVocalsDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
{
var charIdsForVocals = [];
for (charKey in state.currentSongMetadata.playData.playableChars.keys())
{
var charData = state.currentSongMetadata.playData.playableChars.get(charKey);
charIdsForVocals.push(charKey);
if (charData.opponent != null)
charIdsForVocals.push(charData.opponent);
}
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT, true, closable);
var dialogContainer = dialog.findComponent('vocalContainer');
var onDropFile:String->Void;
for (charKey in charIdsForVocals)
{
trace('Adding vocal upload for character ${charKey}');
var charMetadata:BaseCharacter = CharacterDataParser.fetchCharacter(charKey);
var charName:String = charMetadata.characterName;
var vocalsEntry = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
var vocalsEntryLabel:Label = vocalsEntry.findComponent('vocalsEntryLabel', Label);
vocalsEntryLabel.text = 'Click to browse for a vocal track for $charName.';
vocalsEntry.onClick = (_event) ->
{
Dialogs.openBinaryFile('Open $charName Vocals', [{label: "Audio File (.ogg)", extension: "ogg"}], function(selectedFile)
{
if (selectedFile != null)
{
trace('Selected file: ' + selectedFile);
vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n$selectedFile';
// state.loadVocalsFromBytes(selectedFile.bytes);
removeDropHandler(onDropFile);
}
});
}
dialogContainer.addComponent(vocalsEntry);
}
var dialogContinue:Button = dialog.findComponent('dialogContinue', Button);
dialogContinue.onClick = (_event) ->
{
dialog.hideDialog(DialogButton.APPLY);
};
// TODO: Redo the logic for file drop handler to be more robust.
// We need to distinguish which component the mouse is over when the file is dropped.
onDropFile = (path:String) ->
{
trace('Dropped file: ' + path);
};
addDropHandler(onDropFile);
return dialog;
}
/**
* Builds and opens a dialog displaying the user guide, providing guidance and help on how to use the chart editor.
*/
public static inline function openUserGuideDialog(state:ChartEditorState):Void
public static inline function openUserGuideDialog(state:ChartEditorState):Dialog
{
openDialog(state, CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT, true, true);
return openDialog(state, CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT, true, true);
}
/**
@ -155,6 +437,12 @@ class ChartEditorDialogHandler
dialog.closable = closable;
dialog.showDialog(modal);
state.isHaxeUIDialogOpen = true;
dialog.onDialogClosed = (_event) ->
{
state.isHaxeUIDialogOpen = false;
};
return dialog;
}
}

View file

@ -295,6 +295,11 @@ class ChartEditorState extends HaxeUIState
|| Screen.instance.hasSolidComponentUnderPoint(FlxG.mouse.screenX, FlxG.mouse.screenY, haxe.ui.components.Link);
}
/**
* Set by ChartEditorDialogHandler, used to prevent background interaction while the dialog is open.
*/
public var isHaxeUIDialogOpen:Bool = false;
/**
* The variation ID for the difficulty which is currently being edited.
*/
@ -753,7 +758,7 @@ class ChartEditorState extends HaxeUIState
// TODO: We should be loading the music later when the user requests it.
// loadDefaultMusic();
ChartEditorDialogHandler.openSplashDialog(this, false);
ChartEditorDialogHandler.openWelcomeDialog(this, false);
}
function buildDefaultSongData()
@ -974,7 +979,7 @@ class ChartEditorState extends HaxeUIState
// Add functionality to the menu items.
addUIClickListener('menubarItemNewChart', (event:MouseEvent) -> ChartEditorDialogHandler.openSplashDialog(this, true));
addUIClickListener('menubarItemNewChart', (event:MouseEvent) -> ChartEditorDialogHandler.openWelcomeDialog(this, true));
addUIClickListener('menubarItemLoadInst', (event:MouseEvent) -> ChartEditorDialogHandler.openUploadInstDialog(this, true));
addUIClickListener('menubarItemUndo', (event:MouseEvent) -> undoLastCommand());
@ -1168,7 +1173,7 @@ class ChartEditorState extends HaxeUIState
if (FlxG.keys.justPressed.Q)
{
ChartEditorDialogHandler.openSplashDialog(this, true);
ChartEditorDialogHandler.openWelcomeDialog(this, true);
}
// Right align the BF health icon.
@ -2266,7 +2271,7 @@ class ChartEditorState extends HaxeUIState
}
}
if (FlxG.keys.justPressed.SPACE)
if (FlxG.keys.justPressed.SPACE && !isHaxeUIDialogOpen)
{
toggleAudioPlayback();
}
@ -2422,7 +2427,9 @@ class ChartEditorState extends HaxeUIState
*/
public function loadInstrumentalFromBytes(bytes:haxe.io.Bytes):Void
{
audioInstTrack = FlxG.sound.load(openfl.media.Sound.loadCompressedDataFromByteArray(ByteArray.fromBytes(bytes)), 1.0, false);
var openflSound = new openfl.media.Sound();
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length);
audioInstTrack = FlxG.sound.load(openflSound, 1.0, false);
audioInstTrack.autoDestroy = false;
audioInstTrack.pause();
@ -2438,7 +2445,9 @@ class ChartEditorState extends HaxeUIState
// Prevent the time from skipping back to 0 when the song ends.
audioInstTrack.onComplete = function()
{
if (audioInstTrack != null)
audioInstTrack.pause();
if (audioVocalTrack != null)
audioVocalTrack.pause();
};