Merge remote-tracking branch 'origin/rewrite/master' into feature/chart-waveform

This commit is contained in:
EliteMasterEric 2023-12-13 22:36:08 -05:00
commit b740fbf028
15 changed files with 645 additions and 297 deletions

2
assets

@ -1 +1 @@
Subproject commit 3c8ac202bbb93bf84c7dcd0697a9d7121d3c5283 Subproject commit 7d59681870d2b73417a9f5b553720e8b7120bbde

View file

@ -49,14 +49,14 @@
"name": "haxeui-core", "name": "haxeui-core",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "5d4ac180f85b39e72624f4b8d17925d91ebe4278", "ref": "032192e849cdb7d1070c0a3241c58ee555ffaccc",
"url": "https://github.com/haxeui/haxeui-core" "url": "https://github.com/haxeui/haxeui-core"
}, },
{ {
"name": "haxeui-flixel", "name": "haxeui-flixel",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "89a4cf621e5c204922f7a12fbde5d1d84f8b47f5", "ref": "d90758b229d05206400df867d333c79d9fdbd478",
"url": "https://github.com/haxeui/haxeui-flixel" "url": "https://github.com/haxeui/haxeui-flixel"
}, },
{ {

View file

@ -280,6 +280,36 @@ class CharacterDataParser
return characterCache.keys().array(); return characterCache.keys().array();
} }
/**
* TODO: Hardcode this.
*/
public static function getCharPixelIconAsset(char:String):String
{
var icon:String = char;
switch (icon)
{
case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf":
icon = "bf";
case "monster-christmas":
icon = "monster";
case "mom" | "mom-car":
icon = "mommy";
case "pico-blazin" | "pico-playable" | "pico-speaker":
icon = "pico";
case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen":
icon = "gf";
case "dad":
icon = "daddy";
case "darnell-blazin":
icon = "darnell";
case "senpai-angry":
icon = "senpai";
}
return Paths.image("freeplay/icons/" + icon + "pixel");
}
/** /**
* Clears the character data cache. * Clears the character data cache.
*/ */

View file

@ -1,6 +1,9 @@
package funkin.ui.debug.charting; package funkin.ui.debug.charting;
import funkin.util.logging.CrashHandler; import funkin.util.logging.CrashHandler;
import haxe.ui.containers.HBox;
import haxe.ui.containers.Grid;
import haxe.ui.containers.ScrollView;
import haxe.ui.containers.menus.MenuBar; import haxe.ui.containers.menus.MenuBar;
import flixel.addons.display.FlxSliceSprite; import flixel.addons.display.FlxSliceSprite;
import flixel.addons.display.FlxTiledSprite; import flixel.addons.display.FlxTiledSprite;
@ -106,6 +109,8 @@ import haxe.ui.containers.menus.MenuItem;
import haxe.ui.containers.menus.MenuCheckBox; import haxe.ui.containers.menus.MenuCheckBox;
import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeView;
import haxe.ui.containers.TreeViewNode; import haxe.ui.containers.TreeViewNode;
import haxe.ui.components.Image;
import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox;
import haxe.ui.core.Component; import haxe.ui.core.Component;
import haxe.ui.core.Screen; import haxe.ui.core.Screen;
import haxe.ui.events.DragEvent; import haxe.ui.events.DragEvent;
@ -117,6 +122,7 @@ import openfl.display.BitmapData;
import funkin.audio.visualize.PolygonSpectogram; import funkin.audio.visualize.PolygonSpectogram;
import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxGroup.FlxTypedGroup;
import funkin.audio.visualize.PolygonVisGroup; import funkin.audio.visualize.PolygonVisGroup;
import flixel.input.mouse.FlxMouseEvent;
import flixel.text.FlxText; import flixel.text.FlxText;
using Lambda; using Lambda;
@ -142,6 +148,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
public static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata'); 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_EVENTDATA_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_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata');
public static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty'); public static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty');
public static final CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/player-preview'); public static final CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/player-preview');
@ -532,6 +539,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
*/ */
var playtestStartTime:Bool = false; var playtestStartTime:Bool = false;
/**
* If true, playtesting a chart will let you "gameover" / die when you lose ur health!
*/
var playtestPracticeMode:Bool = false;
// Visuals // Visuals
/** /**
@ -2336,6 +2348,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (!Preferences.debugDisplay) menubar.paddingLeft = null; if (!Preferences.debugDisplay) menubar.paddingLeft = null;
this.setupNotifications(); this.setupNotifications();
// Setup character dropdowns.
FlxMouseEvent.add(healthIconDad, function(_) {
if (!isCursorOverHaxeUI)
{
this.openCharacterDropdown(CharacterType.DAD, true);
}
});
FlxMouseEvent.add(healthIconBF, function(_) {
if (!isCursorOverHaxeUI)
{
this.openCharacterDropdown(CharacterType.BF, true);
}
});
} }
/** /**
@ -2376,13 +2403,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else else
{ {
Conductor.currentTimeChange.bpm += 1; Conductor.currentTimeChange.bpm += 1;
refreshMetadataToolbox(); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
} }
} }
playbarBPM.onRightClick = _ -> { playbarBPM.onRightClick = _ -> {
Conductor.currentTimeChange.bpm -= 1; Conductor.currentTimeChange.bpm -= 1;
refreshMetadataToolbox(); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
} }
// Add functionality to the menu items. // Add functionality to the menu items.
@ -2507,9 +2534,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1); menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1);
menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1); menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1);
menubarItemPlaytestStartTime.onChange = event -> playtestStartTime = event.value;
menubarItemPlaytestStartTime.selected = playtestStartTime;
menuBarItemThemeLight.onChange = function(event:UIEvent) { menuBarItemThemeLight.onChange = function(event:UIEvent) {
if (event.target.value) currentTheme = ChartEditorTheme.Light; if (event.target.value) currentTheme = ChartEditorTheme.Light;
}; };
@ -2576,6 +2600,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
menubarItemToggleToolboxMetadata.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_METADATA_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); menubarItemToggleToolboxNotes.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value);
menubarItemToggleToolboxEvents.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT, event.value); menubarItemToggleToolboxEvents.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENTDATA_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); 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); menubarItemToggleToolboxOpponentPreview.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT, event.value);
@ -2650,7 +2675,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
* Open the backups folder in the file explorer. * Open the backups folder in the file explorer.
* Don't call this on HTML5. * Don't call this on HTML5.
*/ */
function openBackupsFolder():Void function openBackupsFolder(?_):Void
{ {
#if sys #if sys
// TODO: Is there a way to open a folder and highlight a file in it? // TODO: Is there a way to open a folder and highlight a file in it?
@ -3439,6 +3464,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var overlapsSelection:Bool = FlxG.mouse.overlaps(renderedSelectionSquares); var overlapsSelection:Bool = FlxG.mouse.overlaps(renderedSelectionSquares);
var overlapsHealthIcons:Bool = FlxG.mouse.overlaps(healthIconBF) || FlxG.mouse.overlaps(healthIconDad);
if (FlxG.mouse.justPressedMiddle) if (FlxG.mouse.justPressedMiddle)
{ {
if (scrollAnchorScreenPos == null) if (scrollAnchorScreenPos == null)
@ -3458,11 +3485,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{ {
scrollAnchorScreenPos = null; scrollAnchorScreenPos = null;
} }
else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea)) else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea) && !isCursorOverHaxeUI)
{ {
gridPlayheadScrollAreaPressed = true; gridPlayheadScrollAreaPressed = true;
} }
else if (notePreview != null && FlxG.mouse.overlaps(notePreview)) else if (notePreview != null && FlxG.mouse.overlaps(notePreview) && !isCursorOverHaxeUI)
{ {
// Clicked note preview // Clicked note preview
notePreviewScrollAreaStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY); notePreviewScrollAreaStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
@ -3914,7 +3941,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (FlxG.mouse.justPressed) if (FlxG.mouse.justPressed)
{ {
// Just clicked to place a note. // Just clicked to place a note.
if (overlapsGrid && !overlapsSelectionBorder) if (!isCursorOverHaxeUI && overlapsGrid && !overlapsSelectionBorder)
{ {
// We clicked on the grid without moving the mouse. // We clicked on the grid without moving the mouse.
@ -4059,7 +4086,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var isOrWillSelect = overlapsSelection || dragTargetNote != null || dragTargetEvent != null; var isOrWillSelect = overlapsSelection || dragTargetNote != null || dragTargetEvent != null;
// Handle grid cursor. // Handle grid cursor.
if (overlapsGrid && !isOrWillSelect && !overlapsSelectionBorder && !gridPlayheadScrollAreaPressed) if (!isCursorOverHaxeUI && overlapsGrid && !isOrWillSelect && !overlapsSelectionBorder && !gridPlayheadScrollAreaPressed)
{ {
// Indicate that we can place a note here. // Indicate that we can place a note here.
@ -4129,6 +4156,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
} }
} }
else else
{
if (!isCursorOverHaxeUI)
{ {
if (notePreview != null && FlxG.mouse.overlaps(notePreview)) if (notePreview != null && FlxG.mouse.overlaps(notePreview))
{ {
@ -4151,6 +4180,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
targetCursorMode = Cell; targetCursorMode = Cell;
} }
} }
else if (overlapsHealthIcons)
{
targetCursorMode = Pointer;
}
}
} }
// Actually set the cursor mode to the one we specified earlier. // Actually set the cursor mode to the one we specified earlier.
@ -4180,7 +4214,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
difficultySelectDirty = false; difficultySelectDirty = false;
// Manage the Select Difficulty tree view. // Manage the Select Difficulty tree view.
var difficultyToolbox:Null<CollapsibleDialog> = this.getToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); var difficultyToolbox:Null<CollapsibleDialog> = this.getToolbox_OLD(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
if (difficultyToolbox == null) return; if (difficultyToolbox == null) return;
var treeView:Null<TreeView> = difficultyToolbox.findComponent('difficultyToolboxTree'); var treeView:Null<TreeView> = difficultyToolbox.findComponent('difficultyToolboxTree');
@ -4227,7 +4261,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handlePlayerPreviewToolbox():Void function handlePlayerPreviewToolbox():Void
{ {
// Manage the Select Difficulty tree view. // Manage the Select Difficulty tree view.
var charPreviewToolbox:Null<CollapsibleDialog> = this.getToolbox(CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT); var charPreviewToolbox:Null<CollapsibleDialog> = this.getToolbox_OLD(CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT);
if (charPreviewToolbox == null) return; if (charPreviewToolbox == null) return;
// TODO: Re-enable the player preview once we figure out the performance issues. // TODO: Re-enable the player preview once we figure out the performance issues.
@ -4263,7 +4297,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handleOpponentPreviewToolbox():Void function handleOpponentPreviewToolbox():Void
{ {
// Manage the Select Difficulty tree view. // Manage the Select Difficulty tree view.
var charPreviewToolbox:Null<CollapsibleDialog> = this.getToolbox(CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT); var charPreviewToolbox:Null<CollapsibleDialog> = this.getToolbox_OLD(CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT);
if (charPreviewToolbox == null) return; if (charPreviewToolbox == null) return;
// TODO: Re-enable the player preview once we figure out the performance issues. // TODO: Re-enable the player preview once we figure out the performance issues.
@ -4581,7 +4615,14 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
} }
// DELETE = Delete // DELETE = Delete
if (FlxG.keys.justPressed.DELETE) var delete:Bool = FlxG.keys.justPressed.DELETE;
// on macbooks, Delete == backspace
#if mac
delete = delete || FlxG.keys.justPressed.BACKSPACE;
#end
if (delete)
{ {
// Delete selected items. // Delete selected items.
if (currentNoteSelection.length > 0 && currentEventSelection.length > 0) if (currentNoteSelection.length > 0 && currentEventSelection.length > 0)
@ -4758,7 +4799,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
targetDifficulty: selectedDifficulty, targetDifficulty: selectedDifficulty,
// TODO: Add this. // TODO: Add this.
// targetCharacter: targetCharacter, // targetCharacter: targetCharacter,
practiceMode: true, practiceMode: playtestPracticeMode,
minimalMode: minimal, minimalMode: minimal,
startTimestamp: startTimestamp, startTimestamp: startTimestamp,
overrideMusic: true, overrideMusic: true,
@ -5013,7 +5054,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
Conductor.mapTimeChanges(this.currentSongMetadata.timeChanges); Conductor.mapTimeChanges(this.currentSongMetadata.timeChanges);
refreshDifficultyTreeSelection(); refreshDifficultyTreeSelection();
refreshMetadataToolbox(); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
} }
else else
{ {
@ -5022,7 +5063,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
selectedDifficulty = prevDifficulty; selectedDifficulty = prevDifficulty;
refreshDifficultyTreeSelection(); refreshDifficultyTreeSelection();
refreshMetadataToolbox(); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
} }
} }
else else
@ -5041,7 +5082,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
selectedDifficulty = nextDifficulty; selectedDifficulty = nextDifficulty;
refreshDifficultyTreeSelection(); refreshDifficultyTreeSelection();
refreshMetadataToolbox(); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
} }
else else
{ {
@ -5050,7 +5091,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
selectedDifficulty = nextDifficulty; selectedDifficulty = nextDifficulty;
refreshDifficultyTreeSelection(); refreshDifficultyTreeSelection();
refreshMetadataToolbox(); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
} }
} }
@ -5163,7 +5204,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (treeView == null) if (treeView == null)
{ {
// Manage the Select Difficulty tree view. // Manage the Select Difficulty tree view.
var difficultyToolbox:Null<CollapsibleDialog> = this.getToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); var difficultyToolbox:Null<CollapsibleDialog> = this.getToolbox_OLD(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
if (difficultyToolbox == null) return; if (difficultyToolbox == null) return;
treeView = difficultyToolbox.findComponent('difficultyToolboxTree'); treeView = difficultyToolbox.findComponent('difficultyToolboxTree');
@ -5183,7 +5224,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{ {
if (treeView == null) if (treeView == null)
{ {
var difficultyToolbox:Null<CollapsibleDialog> = this.getToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); var difficultyToolbox:Null<CollapsibleDialog> = this.getToolbox_OLD(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
if (difficultyToolbox == null) return null; if (difficultyToolbox == null) return null;
treeView = difficultyToolbox.findComponent('difficultyToolboxTree'); treeView = difficultyToolbox.findComponent('difficultyToolboxTree');
@ -5227,8 +5268,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
trace('Changing difficulty to "$variation:$difficulty"'); trace('Changing difficulty to "$variation:$difficulty"');
selectedVariation = variation; selectedVariation = variation;
selectedDifficulty = difficulty; selectedDifficulty = difficulty;
// refreshDifficultyTreeSelection(treeView); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
refreshMetadataToolbox();
} }
// case 'song': // case 'song':
// case 'variation': // case 'variation':
@ -5237,82 +5277,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
trace('Selected wrong node type, resetting selection.'); trace('Selected wrong node type, resetting selection.');
var currentTreeDifficultyNode = getCurrentTreeDifficultyNode(treeView); var currentTreeDifficultyNode = getCurrentTreeDifficultyNode(treeView);
if (currentTreeDifficultyNode != null) treeView.selectedNode = currentTreeDifficultyNode; if (currentTreeDifficultyNode != null) treeView.selectedNode = currentTreeDifficultyNode;
refreshMetadataToolbox(); this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
}
/**
* When the difficulty changes, update the song metadata toolbox to reflect the new data.
*/
function refreshMetadataToolbox():Void
{
var toolbox:Null<CollapsibleDialog> = this.getToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
if (toolbox == null) return;
var inputSongName:Null<TextField> = toolbox.findComponent('inputSongName', TextField);
if (inputSongName != null) inputSongName.value = currentSongMetadata.songName;
var inputSongArtist:Null<TextField> = toolbox.findComponent('inputSongArtist', TextField);
if (inputSongArtist != null) inputSongArtist.value = currentSongMetadata.artist;
var inputStage:Null<DropDown> = toolbox.findComponent('inputStage', DropDown);
if (inputStage != null) inputStage.value = currentSongMetadata.playData.stage;
var inputNoteStyle:Null<DropDown> = toolbox.findComponent('inputNoteStyle', DropDown);
if (inputNoteStyle != null) inputNoteStyle.value = currentSongMetadata.playData.noteStyle;
var inputBPM:Null<NumberStepper> = toolbox.findComponent('inputBPM', NumberStepper);
if (inputBPM != null) inputBPM.value = currentSongMetadata.timeChanges[0].bpm;
var labelScrollSpeed:Null<Label> = toolbox.findComponent('labelScrollSpeed', Label);
if (labelScrollSpeed != null) labelScrollSpeed.text = 'Scroll Speed: ${currentSongChartScrollSpeed}x';
var inputScrollSpeed:Null<Slider> = toolbox.findComponent('inputScrollSpeed', Slider);
if (inputScrollSpeed != null) inputScrollSpeed.value = currentSongChartScrollSpeed;
var frameVariation:Null<Frame> = toolbox.findComponent('frameVariation', Frame);
if (frameVariation != null) frameVariation.text = 'Variation: ${selectedVariation.toTitleCase()}';
var frameDifficulty:Null<Frame> = toolbox.findComponent('frameDifficulty', Frame);
if (frameDifficulty != null) frameDifficulty.text = 'Difficulty: ${selectedDifficulty.toTitleCase()}';
var inputStage:Null<DropDown> = toolbox.findComponent('inputStage', DropDown);
var stageId:String = currentSongMetadata.playData.stage;
var stageData:Null<StageData> = StageDataParser.parseStageData(stageId);
if (inputStage != null)
{
inputStage.value = (stageData != null) ?
{id: stageId, text: stageData.name} :
{id: "mainStage", text: "Main Stage"};
}
var inputCharacterPlayer:Null<DropDown> = toolbox.findComponent('inputCharacterPlayer', DropDown);
var charIdPlayer:String = currentSongMetadata.playData.characters.player;
var charDataPlayer:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charIdPlayer);
if (inputCharacterPlayer != null)
{
inputCharacterPlayer.value = (charDataPlayer != null) ?
{id: charIdPlayer, text: charDataPlayer.name} :
{id: "bf", text: "Boyfriend"};
}
var inputCharacterOpponent:Null<DropDown> = toolbox.findComponent('inputCharacterOpponent', DropDown);
var charIdOpponent:String = currentSongMetadata.playData.characters.opponent;
var charDataOpponent:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charIdOpponent);
if (inputCharacterOpponent != null)
{
inputCharacterOpponent.value = (charDataOpponent != null) ?
{id: charIdOpponent, text: charDataOpponent.name} :
{id: "dad", text: "Dad"};
}
var inputCharacterGirlfriend:Null<DropDown> = toolbox.findComponent('inputCharacterGirlfriend', DropDown);
var charIdGirlfriend:String = currentSongMetadata.playData.characters.girlfriend;
var charDataGirlfriend:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charIdGirlfriend);
if (inputCharacterGirlfriend != null)
{
inputCharacterGirlfriend.value = (charDataGirlfriend != null) ?
{id: charIdGirlfriend, text: charDataGirlfriend.name} :
{id: "none", text: "None"};
} }
} }

View file

@ -2,8 +2,12 @@ package funkin.ui.debug.charting.dialogs;
import haxe.ui.containers.dialogs.Dialog; import haxe.ui.containers.dialogs.Dialog;
import haxe.ui.containers.dialogs.Dialog.DialogEvent; import haxe.ui.containers.dialogs.Dialog.DialogEvent;
import haxe.ui.animation.AnimationBuilder;
import haxe.ui.styles.EasingFunction;
import haxe.ui.core.Component; import haxe.ui.core.Component;
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
@:access(funkin.ui.debug.charting.ChartEditorState) @:access(funkin.ui.debug.charting.ChartEditorState)
class ChartEditorBaseDialog extends Dialog class ChartEditorBaseDialog extends Dialog
{ {
@ -25,6 +29,18 @@ class ChartEditorBaseDialog extends Dialog
this.onDialogClosed = event -> onClose(event); this.onDialogClosed = event -> onClose(event);
} }
public override function showDialog(modal:Bool = true):Void
{
super.showDialog(modal);
fadeInComponent(this, 1);
}
private override function onReady():Void
{
_overlay.opacity = 0;
fadeInDialogOverlay();
}
/** /**
* Called when the dialog is closed. * Called when the dialog is closed.
* Override this to add custom behavior. * Override this to add custom behavior.
@ -54,6 +70,36 @@ class ChartEditorBaseDialog extends Dialog
this.closable = params.closable ?? false; this.closable = params.closable ?? false;
} }
static final OVERLAY_EASE_DURATION:Float = 0.2;
static final OVERLAY_EASE_TYPE:String = "easeOut";
function fadeInDialogOverlay():Void
{
if (!modal)
{
trace('Dialog is not modal, skipping overlay fade...');
return;
}
if (_overlay == null)
{
trace('[WARN] Dialog overlay is null, skipping overlay fade...');
return;
}
fadeInComponent(_overlay, 0.5);
}
function fadeInComponent(component:Component, fadeTo:Float = 1):Void
{
var builder = new AnimationBuilder(component, OVERLAY_EASE_DURATION, OVERLAY_EASE_TYPE);
builder.setPosition(0, "opacity", 0, true); // 0% absolute
builder.setPosition(100, "opacity", fadeTo, true);
trace('Fading in dialog component...');
builder.play();
}
} }
typedef DialogParams = typedef DialogParams =

View file

@ -0,0 +1,24 @@
package funkin.ui.debug.charting.dialogs;
import haxe.ui.containers.dialogs.Dialog;
import haxe.ui.containers.dialogs.Dialog.DialogEvent;
import haxe.ui.animation.AnimationBuilder;
import haxe.ui.styles.EasingFunction;
import haxe.ui.core.Component;
import haxe.ui.containers.menus.Menu;
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
@:access(funkin.ui.debug.charting.ChartEditorState)
class ChartEditorBaseMenu extends Menu
{
var state:ChartEditorState;
public function new(state:ChartEditorState)
{
super();
this.state = state;
// this.destroyOnClose = true;
}
}

View file

@ -0,0 +1,129 @@
package funkin.ui.debug.charting.dialogs;
import flixel.math.FlxPoint;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData;
import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.components.HealthIcon;
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogParams;
import funkin.util.SortUtil;
import haxe.ui.components.Label;
import haxe.ui.containers.Grid;
import haxe.ui.containers.HBox;
import haxe.ui.containers.ScrollView;
import haxe.ui.containers.ScrollView;
import haxe.ui.core.Screen;
// @: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/dialogs/character-icon-selector.xml"))
class ChartEditorCharacterIconSelectorMenu extends ChartEditorBaseMenu
{
public var charSelectScroll:ScrollView;
public var charIconName:Label;
public function new(state2:ChartEditorState, charType:CharacterType, lockPosition:Bool = false)
{
super(state2);
initialize(charType, lockPosition);
}
function initialize(charType:CharacterType, lockPosition:Bool)
{
var currentCharId:String = switch (charType)
{
case BF: state.currentSongMetadata.playData.characters.player;
case GF: state.currentSongMetadata.playData.characters.girlfriend;
case DAD: state.currentSongMetadata.playData.characters.opponent;
default: throw 'Invalid charType: ' + charType;
};
// Position this menu.
var targetHealthIcon:Null<HealthIcon> = switch (charType)
{
case BF: state.healthIconBF;
case DAD: state.healthIconDad;
default: null;
};
if (lockPosition && targetHealthIcon != null)
{
var healthIconBottomCenter:FlxPoint = new FlxPoint(targetHealthIcon.x + targetHealthIcon.width / 2, targetHealthIcon.y + targetHealthIcon.height);
this.x = healthIconBottomCenter.x - this.width / 2;
this.y = healthIconBottomCenter.y;
}
else
{
this.x = Screen.instance.currentMouseX;
this.y = Screen.instance.currentMouseY;
}
var charGrid = new Grid();
charGrid.columns = 5;
charGrid.width = 100;
charSelectScroll.addComponent(charGrid);
var charIds:Array<String> = CharacterDataParser.listCharacterIds();
charIds.sort(SortUtil.alphabetically);
var defaultText:String = '(choose a character)';
for (charIndex => charId in charIds)
{
var charData:CharacterData = CharacterDataParser.fetchCharacterData(charId);
var charButton = new haxe.ui.components.Button();
charButton.width = 70;
charButton.height = 70;
charButton.padding = 8;
charButton.iconPosition = "top";
if (charId == currentCharId)
{
// Scroll to the character if it is already selected.
charSelectScroll.hscrollPos = Math.floor(charIndex / 5) * 80;
charButton.selected = true;
defaultText = '${charData.name} [${charId}]';
}
var LIMIT = 6;
charButton.icon = CharacterDataParser.getCharPixelIconAsset(charId);
charButton.text = charData.name.length > LIMIT ? '${charData.name.substr(0, LIMIT)}.' : '${charData.name}';
charButton.onClick = _ -> {
switch (charType)
{
case BF: state.currentSongMetadata.playData.characters.player = charId;
case GF: state.currentSongMetadata.playData.characters.girlfriend = charId;
case DAD: state.currentSongMetadata.playData.characters.opponent = charId;
default: throw 'Invalid charType: ' + charType;
};
state.healthIconsDirty = true;
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
};
charButton.onMouseOver = _ -> {
charIconName.text = '${charData.name} [${charId}]';
};
charButton.onMouseOut = _ -> {
charIconName.text = defaultText;
};
charGrid.addComponent(charButton);
}
charIconName.text = defaultText;
}
public static function build(state2:ChartEditorState, charType:CharacterType, lockPosition:Bool = false):ChartEditorCharacterIconSelectorMenu
{
var menu = new ChartEditorCharacterIconSelectorMenu(state2, charType, lockPosition);
Screen.instance.addComponent(menu);
return menu;
}
}

View file

@ -11,6 +11,7 @@ import haxe.ui.containers.dialogs.Dialogs;
import haxe.ui.notifications.NotificationManager; import haxe.ui.notifications.NotificationManager;
import haxe.ui.notifications.NotificationType; import haxe.ui.notifications.NotificationType;
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/upload-chart.xml")) @:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/upload-chart.xml"))
class ChartEditorUploadChartDialog extends ChartEditorBaseDialog class ChartEditorUploadChartDialog extends ChartEditorBaseDialog
{ {

View file

@ -16,6 +16,7 @@ import funkin.play.song.Song;
import funkin.play.stage.StageData; import funkin.play.stage.StageData;
import funkin.ui.debug.charting.dialogs.ChartEditorAboutDialog; import funkin.ui.debug.charting.dialogs.ChartEditorAboutDialog;
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget; import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget;
import funkin.ui.debug.charting.dialogs.ChartEditorCharacterIconSelectorMenu;
import funkin.ui.debug.charting.dialogs.ChartEditorUploadChartDialog; import funkin.ui.debug.charting.dialogs.ChartEditorUploadChartDialog;
import funkin.ui.debug.charting.dialogs.ChartEditorWelcomeDialog; import funkin.ui.debug.charting.dialogs.ChartEditorWelcomeDialog;
import funkin.ui.debug.charting.util.ChartEditorDropdowns; import funkin.ui.debug.charting.util.ChartEditorDropdowns;
@ -39,6 +40,7 @@ import haxe.ui.containers.dialogs.Dialog;
import haxe.ui.containers.dialogs.Dialog.DialogButton; import haxe.ui.containers.dialogs.Dialog.DialogButton;
import haxe.ui.containers.dialogs.Dialogs; import haxe.ui.containers.dialogs.Dialogs;
import haxe.ui.containers.Form; import haxe.ui.containers.Form;
import haxe.ui.containers.menus.Menu;
import haxe.ui.containers.VBox; import haxe.ui.containers.VBox;
import haxe.ui.core.Component; import haxe.ui.core.Component;
import haxe.ui.events.UIEvent; import haxe.ui.events.UIEvent;
@ -286,6 +288,15 @@ class ChartEditorDialogHandler
}; };
} }
public static function openCharacterDropdown(state:ChartEditorState, charType:CharacterType, lockPosition:Bool = false):Null<Menu>
{
var menu = ChartEditorCharacterIconSelectorMenu.build(state, charType, lockPosition);
menu.zIndex = 1000;
return menu;
}
public static function openCreateSongWizardBasicOnly(state:ChartEditorState, closable:Bool):Void public static function openCreateSongWizardBasicOnly(state:ChartEditorState, closable:Bool):Void
{ {
// Step 1. Song Metadata // Step 1. Song Metadata

View file

@ -99,7 +99,7 @@ class ChartEditorImportExportHandler
state.switchToCurrentInstrumental(); state.switchToCurrentInstrumental();
state.postLoadInstrumental(); state.postLoadInstrumental();
state.refreshMetadataToolbox(); state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
state.success('Success', 'Loaded song (${rawSongMetadata[0].songName})'); state.success('Success', 'Loaded song (${rawSongMetadata[0].songName})');
} }

View file

@ -5,6 +5,7 @@ import haxe.ui.containers.HBox;
import haxe.ui.notifications.Notification; import haxe.ui.notifications.Notification;
import haxe.ui.notifications.NotificationManager; import haxe.ui.notifications.NotificationManager;
import haxe.ui.notifications.NotificationType; import haxe.ui.notifications.NotificationType;
import haxe.ui.notifications.NotificationData.NotificationActionData;
class ChartEditorNotificationHandler class ChartEditorNotificationHandler
{ {
@ -77,7 +78,7 @@ class ChartEditorNotificationHandler
* @param actions The actions to add to the notification. * @param actions The actions to add to the notification.
* @return The notification that was sent. * @return The notification that was sent.
*/ */
public static function infoWithActions(state:ChartEditorState, title:String, body:String, actions:Array<NotificationAction>):Notification public static function infoWithActions(state:ChartEditorState, title:String, body:String, actions:Array<NotificationActionData>):Notification
{ {
return sendNotification(state, title, body, NotificationType.Info, actions); return sendNotification(state, title, body, NotificationType.Info, actions);
} }
@ -101,7 +102,8 @@ class ChartEditorNotificationHandler
NotificationManager.instance.removeNotification(notif); NotificationManager.instance.removeNotification(notif);
} }
static function sendNotification(state:ChartEditorState, title:String, body:String, ?type:NotificationType, ?actions:Array<NotificationAction>):Notification static function sendNotification(state:ChartEditorState, title:String, body:String, ?type:NotificationType,
?actions:Array<NotificationActionData>):Notification
{ {
var actionNames:Array<String> = actions == null ? [] : actions.map(action -> action.text); var actionNames:Array<String> = actions == null ? [] : actions.map(action -> action.text);
@ -111,10 +113,10 @@ class ChartEditorNotificationHandler
body: body, body: body,
type: type ?? NotificationType.Default, type: type ?? NotificationType.Default,
expiryMs: Constants.NOTIFICATION_DISMISS_TIME, expiryMs: Constants.NOTIFICATION_DISMISS_TIME,
actions: actionNames actions: actions
}); });
if (actionNames.length > 0) if (actions != null && actions.length > 0)
{ {
// TODO: Tell Ian that this is REALLY dumb. // TODO: Tell Ian that this is REALLY dumb.
var actionsContainer:HBox = notif.findComponent('actionsContainer', HBox); var actionsContainer:HBox = notif.findComponent('actionsContainer', HBox);
@ -122,13 +124,13 @@ class ChartEditorNotificationHandler
if (Std.isOfType(component, Button)) if (Std.isOfType(component, Button))
{ {
var button:Button = cast component; var button:Button = cast component;
var action:Null<NotificationAction> = actions.find(action -> action.text == button.text); var action:Null<NotificationActionData> = actions.find(action -> action.text == button.text);
if (action != null && action.callback != null) if (action != null && action.callback != null)
{ {
button.onClick = function(_) { button.onClick = function(_) {
// Don't allow actions to be clicked while the playtest is open. // Don't allow actions to be clicked while the playtest is open.
if (state.subState != null) return; if (state.subState != null) return;
action.callback(); action.callback(action);
}; };
} }
} }

View file

@ -11,7 +11,6 @@ import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.event.SongEvent; import funkin.play.event.SongEvent;
import funkin.data.event.SongEventData; import funkin.data.event.SongEventData;
import funkin.data.song.SongData.SongTimeChange; import funkin.data.song.SongData.SongTimeChange;
import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand;
import funkin.play.character.BaseCharacter.CharacterType; import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData; import funkin.play.character.CharacterData;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
@ -35,6 +34,8 @@ import haxe.ui.containers.Box;
import haxe.ui.containers.dialogs.CollapsibleDialog; import haxe.ui.containers.dialogs.CollapsibleDialog;
import haxe.ui.containers.dialogs.Dialog.DialogButton; import haxe.ui.containers.dialogs.Dialog.DialogButton;
import haxe.ui.containers.dialogs.Dialog.DialogEvent; import haxe.ui.containers.dialogs.Dialog.DialogEvent;
import funkin.ui.debug.charting.toolboxes.ChartEditorBaseToolbox;
import funkin.ui.debug.charting.toolboxes.ChartEditorMetadataToolbox;
import haxe.ui.containers.Frame; import haxe.ui.containers.Frame;
import haxe.ui.containers.Grid; import haxe.ui.containers.Grid;
import haxe.ui.containers.TreeView; import haxe.ui.containers.TreeView;
@ -80,10 +81,13 @@ class ChartEditorToolboxHandler
onShowToolboxNoteData(state, toolbox); onShowToolboxNoteData(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
onShowToolboxEventData(state, toolbox); onShowToolboxEventData(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:
onShowToolboxPlaytestProperties(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:
onShowToolboxDifficulty(state, toolbox); onShowToolboxDifficulty(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
onShowToolboxMetadata(state, toolbox); // TODO: Fix this.
cast(toolbox, ChartEditorBaseToolbox).refresh();
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT:
onShowToolboxPlayerPreview(state, toolbox); onShowToolboxPlayerPreview(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
@ -117,6 +121,8 @@ class ChartEditorToolboxHandler
onHideToolboxNoteData(state, toolbox); onHideToolboxNoteData(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
onHideToolboxEventData(state, toolbox); onHideToolboxEventData(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:
onHideToolboxPlaytestProperties(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:
onHideToolboxDifficulty(state, toolbox); onHideToolboxDifficulty(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
@ -136,6 +142,22 @@ class ChartEditorToolboxHandler
} }
} }
public static function refreshToolbox(state:ChartEditorState, id:String):Void
{
var toolbox:Null<ChartEditorBaseToolbox> = cast state.activeToolboxes.get(id);
if (toolbox == null) return;
if (toolbox != null)
{
toolbox.refresh();
}
else
{
trace('ChartEditorToolboxHandler.refreshToolbox() - Could not retrieve toolbox: $id');
}
}
public static function rememberOpenToolboxes(state:ChartEditorState):Void {} public static function rememberOpenToolboxes(state:ChartEditorState):Void {}
public static function openRememberedToolboxes(state:ChartEditorState):Void {} public static function openRememberedToolboxes(state:ChartEditorState):Void {}
@ -175,6 +197,8 @@ class ChartEditorToolboxHandler
toolbox = buildToolboxNoteDataLayout(state); toolbox = buildToolboxNoteDataLayout(state);
case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT:
toolbox = buildToolboxEventDataLayout(state); toolbox = buildToolboxEventDataLayout(state);
case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:
toolbox = buildToolboxPlaytestPropertiesLayout(state);
case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:
toolbox = buildToolboxDifficultyLayout(state); toolbox = buildToolboxDifficultyLayout(state);
case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT: case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:
@ -205,7 +229,19 @@ class ChartEditorToolboxHandler
* @param id The asset ID of the toolbox layout. * @param id The asset ID of the toolbox layout.
* @return The toolbox. * @return The toolbox.
*/ */
public static function getToolbox(state:ChartEditorState, id:String):Null<CollapsibleDialog> public static function getToolbox_OLD(state:ChartEditorState, id:String):Null<CollapsibleDialog>
{
var toolbox:Null<CollapsibleDialog> = state.activeToolboxes.get(id);
// Initialize the toolbox without showing it.
if (toolbox == null) toolbox = initToolbox(state, id);
if (toolbox == null) throw 'ChartEditorToolboxHandler.getToolbox_OLD() - Could not retrieve or build toolbox: $id';
return toolbox;
}
public static function getToolbox(state:ChartEditorState, id:String):Null<ChartEditorBaseToolbox>
{ {
var toolbox:Null<CollapsibleDialog> = state.activeToolboxes.get(id); var toolbox:Null<CollapsibleDialog> = state.activeToolboxes.get(id);
@ -214,7 +250,7 @@ class ChartEditorToolboxHandler
if (toolbox == null) throw 'ChartEditorToolboxHandler.getToolbox() - Could not retrieve or build toolbox: $id'; if (toolbox == null) throw 'ChartEditorToolboxHandler.getToolbox() - Could not retrieve or build toolbox: $id';
return toolbox; return cast toolbox;
} }
static function buildToolboxNoteDataLayout(state:ChartEditorState):Null<CollapsibleDialog> static function buildToolboxNoteDataLayout(state:ChartEditorState):Null<CollapsibleDialog>
@ -321,8 +357,12 @@ class ChartEditorToolboxHandler
static function onShowToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} static function onShowToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
static function onShowToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
static function onHideToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} static function onHideToolboxEventData(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
static function onHideToolboxPlaytestProperties(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
static function buildEventDataFormFromSchema(state:ChartEditorState, target:Box, schema:SongEventSchema):Void static function buildEventDataFormFromSchema(state:ChartEditorState, target:Box, schema:SongEventSchema):Void
{ {
trace(schema); trace(schema);
@ -418,6 +458,39 @@ class ChartEditorToolboxHandler
} }
} }
static function buildToolboxPlaytestPropertiesLayout(state:ChartEditorState):Null<CollapsibleDialog>
{
// fill with playtest properties
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT);
if (toolbox == null) return null;
toolbox.onDialogClosed = function(_) {
state.menubarItemToggleToolboxPlaytestProperties.selected = false;
}
var checkboxPracticeMode:Null<CheckBox> = toolbox.findComponent('practiceModeCheckbox', CheckBox);
if (checkboxPracticeMode == null) throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find practiceModeCheckbox component.';
checkboxPracticeMode.selected = state.playtestPracticeMode;
checkboxPracticeMode.onClick = _ -> {
state.playtestPracticeMode = checkboxPracticeMode.selected;
};
var checkboxStartTime:Null<CheckBox> = toolbox.findComponent('playtestStartTimeCheckbox', CheckBox);
if (checkboxStartTime == null)
throw 'ChartEditorToolboxHandler.buildToolboxPlaytestPropertiesLayout() - Could not find playtestStartTimeCheckbox component.';
checkboxStartTime.selected = state.playtestStartTime;
checkboxStartTime.onClick = _ -> {
state.playtestStartTime = checkboxStartTime.selected;
};
return toolbox;
}
static function buildToolboxDifficultyLayout(state:ChartEditorState):Null<CollapsibleDialog> static function buildToolboxDifficultyLayout(state:ChartEditorState):Null<CollapsibleDialog>
{ {
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
@ -505,180 +578,15 @@ class ChartEditorToolboxHandler
static function onHideToolboxDifficulty(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} static function onHideToolboxDifficulty(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
static function buildToolboxMetadataLayout(state:ChartEditorState):Null<CollapsibleDialog> static function buildToolboxMetadataLayout(state:ChartEditorState):Null<ChartEditorBaseToolbox>
{ {
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT); var toolbox:ChartEditorBaseToolbox = ChartEditorMetadataToolbox.build(state);
if (toolbox == null) return null; if (toolbox == null) return null;
// Starting position.
toolbox.x = 150;
toolbox.y = 250;
toolbox.onDialogClosed = function(event:UIEvent) {
state.menubarItemToggleToolboxMetadata.selected = false;
}
var inputSongName:Null<TextField> = toolbox.findComponent('inputSongName', TextField);
if (inputSongName == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputSongName component.';
inputSongName.onChange = function(event:UIEvent) {
var valid:Bool = event.target.text != null && event.target.text != '';
if (valid)
{
inputSongName.removeClass('invalid-value');
state.currentSongMetadata.songName = event.target.text;
}
else
{
state.currentSongMetadata.songName = '';
}
};
inputSongName.value = state.currentSongMetadata.songName;
var inputSongArtist:Null<TextField> = toolbox.findComponent('inputSongArtist', TextField);
if (inputSongArtist == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputSongArtist component.';
inputSongArtist.onChange = function(event:UIEvent) {
var valid:Bool = event.target.text != null && event.target.text != '';
if (valid)
{
inputSongArtist.removeClass('invalid-value');
state.currentSongMetadata.artist = event.target.text;
}
else
{
state.currentSongMetadata.artist = '';
}
};
inputSongArtist.value = state.currentSongMetadata.artist;
var inputStage:Null<DropDown> = toolbox.findComponent('inputStage', DropDown);
if (inputStage == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputStage component.';
inputStage.onChange = function(event:UIEvent) {
var valid:Bool = event.data != null && event.data.id != null;
if (valid)
{
state.currentSongMetadata.playData.stage = event.data.id;
}
};
var startingValueStage = ChartEditorDropdowns.populateDropdownWithStages(inputStage, state.currentSongMetadata.playData.stage);
inputStage.value = startingValueStage;
var inputNoteStyle:Null<DropDown> = toolbox.findComponent('inputNoteStyle', DropDown);
if (inputNoteStyle == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputNoteStyle component.';
inputNoteStyle.onChange = function(event:UIEvent) {
if (event.data?.id == null) return;
state.currentSongNoteStyle = event.data.id;
};
inputNoteStyle.value = state.currentSongNoteStyle;
// By using this flag, we prevent the dropdown value from changing while it is being populated.
var inputCharacterPlayer:Null<DropDown> = toolbox.findComponent('inputCharacterPlayer', DropDown);
if (inputCharacterPlayer == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterPlayer component.';
inputCharacterPlayer.onChange = function(event:UIEvent) {
if (event.data?.id == null) return;
state.currentSongMetadata.playData.characters.player = event.data.id;
};
var startingValuePlayer = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterPlayer, CharacterType.BF,
state.currentSongMetadata.playData.characters.player);
inputCharacterPlayer.value = startingValuePlayer;
var inputCharacterOpponent:Null<DropDown> = toolbox.findComponent('inputCharacterOpponent', DropDown);
if (inputCharacterOpponent == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterOpponent component.';
inputCharacterOpponent.onChange = function(event:UIEvent) {
if (event.data?.id == null) return;
state.currentSongMetadata.playData.characters.opponent = event.data.id;
};
var startingValueOpponent = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterOpponent, CharacterType.DAD,
state.currentSongMetadata.playData.characters.opponent);
inputCharacterOpponent.value = startingValueOpponent;
var inputCharacterGirlfriend:Null<DropDown> = toolbox.findComponent('inputCharacterGirlfriend', DropDown);
if (inputCharacterGirlfriend == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputCharacterGirlfriend component.';
inputCharacterGirlfriend.onChange = function(event:UIEvent) {
if (event.data?.id == null) return;
state.currentSongMetadata.playData.characters.girlfriend = event.data.id == "none" ? "" : event.data.id;
};
var startingValueGirlfriend = ChartEditorDropdowns.populateDropdownWithCharacters(inputCharacterGirlfriend, CharacterType.GF,
state.currentSongMetadata.playData.characters.girlfriend);
inputCharacterGirlfriend.value = startingValueGirlfriend;
var inputBPM:Null<NumberStepper> = toolbox.findComponent('inputBPM', NumberStepper);
if (inputBPM == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputBPM component.';
inputBPM.onChange = function(event:UIEvent) {
if (event.value == null || event.value <= 0) return;
// Use a command so we can undo/redo this action.
var startingBPM = state.currentSongMetadata.timeChanges[0].bpm;
if (event.value != startingBPM)
{
state.performCommand(new ChangeStartingBPMCommand(event.value));
}
};
inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm;
var inputOffsetInst:Null<NumberStepper> = toolbox.findComponent('inputOffsetInst', NumberStepper);
if (inputOffsetInst == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputOffsetInst component.';
inputOffsetInst.onChange = function(event:UIEvent) {
if (event.value == null) return;
state.currentInstrumentalOffset = event.value;
Conductor.instrumentalOffset = event.value;
// Update song length.
state.songLengthInMs = (state.audioInstTrack?.length ?? 1000.0) + Conductor.instrumentalOffset;
};
inputOffsetInst.value = state.currentInstrumentalOffset;
var inputOffsetVocal:Null<NumberStepper> = toolbox.findComponent('inputOffsetVocal', NumberStepper);
if (inputOffsetVocal == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputOffsetVocal component.';
inputOffsetVocal.onChange = function(event:UIEvent) {
if (event.value == null) return;
state.currentSongMetadata.offsets.setVocalOffset(state.currentSongMetadata.playData.characters.player, event.value);
};
inputOffsetVocal.value = state.currentSongMetadata.offsets.getVocalOffset(state.currentSongMetadata.playData.characters.player);
var labelScrollSpeed:Null<Label> = toolbox.findComponent('labelScrollSpeed', Label);
if (labelScrollSpeed == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find labelScrollSpeed component.';
var inputScrollSpeed:Null<Slider> = toolbox.findComponent('inputScrollSpeed', Slider);
if (inputScrollSpeed == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find inputScrollSpeed component.';
inputScrollSpeed.onChange = function(event:UIEvent) {
var valid:Bool = event.target.value != null && event.target.value > 0;
if (valid)
{
inputScrollSpeed.removeClass('invalid-value');
state.currentSongChartScrollSpeed = event.target.value;
}
else
{
state.currentSongChartScrollSpeed = 1.0;
}
labelScrollSpeed.text = 'Scroll Speed: ${state.currentSongChartScrollSpeed}x';
};
inputScrollSpeed.value = state.currentSongChartScrollSpeed;
labelScrollSpeed.text = 'Scroll Speed: ${state.currentSongChartScrollSpeed}x';
var frameVariation:Null<Frame> = toolbox.findComponent('frameVariation', Frame);
if (frameVariation == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find frameVariation component.';
frameVariation.text = 'Variation: ${state.selectedVariation.toTitleCase()}';
var frameDifficulty:Null<Frame> = toolbox.findComponent('frameDifficulty', Frame);
if (frameDifficulty == null) throw 'ChartEditorToolboxHandler.buildToolboxMetadataLayout() - Could not find frameDifficulty component.';
frameDifficulty.text = 'Difficulty: ${state.selectedDifficulty.toTitleCase()}';
return toolbox; return toolbox;
} }
static function onShowToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void
{
state.refreshMetadataToolbox();
}
static function onHideToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void {} static function onHideToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Null<CollapsibleDialog> static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Null<CollapsibleDialog>

View file

@ -0,0 +1,28 @@
package funkin.ui.debug.charting.toolboxes;
import haxe.ui.containers.dialogs.Dialog;
import haxe.ui.containers.dialogs.CollapsibleDialog;
import haxe.ui.containers.dialogs.Dialog.DialogEvent;
import haxe.ui.core.Component;
/**
* The base class for the Toolboxes (manipulatable, arrangeable control windows) in the Chart Editor.
*/
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
@:access(funkin.ui.debug.charting.ChartEditorState)
class ChartEditorBaseToolbox extends CollapsibleDialog
{
var state:ChartEditorState;
private function new(state:ChartEditorState)
{
super();
this.state = state;
}
/**
* Override to implement this.
*/
public function refresh() {}
}

View file

@ -0,0 +1,203 @@
package funkin.ui.debug.charting.toolboxes;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData;
import funkin.play.stage.StageData;
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.components.TextField;
import haxe.ui.containers.Box;
import haxe.ui.containers.Frame;
import haxe.ui.events.UIEvent;
/**
* 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/metadata.xml"))
class ChartEditorMetadataToolbox extends ChartEditorBaseToolbox
{
var inputSongName:TextField;
var inputSongArtist:TextField;
var inputStage:DropDown;
var inputNoteStyle:DropDown;
var buttonCharacterPlayer:Button;
var buttonCharacterGirlfriend:Button;
var buttonCharacterOpponent:Button;
var inputBPM:NumberStepper;
var inputOffsetInst:NumberStepper;
var inputOffsetVocal:NumberStepper;
var labelScrollSpeed:Label;
var inputScrollSpeed:Slider;
var frameVariation:Frame;
var frameDifficulty:Frame;
public function new(state2:ChartEditorState)
{
super(state2);
initialize();
this.onDialogClosed = onClose;
}
function onClose(event:UIEvent)
{
state.menubarItemToggleToolboxMetadata.selected = false;
}
function initialize():Void
{
// Starting position.
// TODO: Save and load this.
this.x = 150;
this.y = 250;
inputSongName.onChange = function(event:UIEvent) {
var valid:Bool = event.target.text != null && event.target.text != '';
if (valid)
{
inputSongName.removeClass('invalid-value');
state.currentSongMetadata.songName = event.target.text;
}
else
{
state.currentSongMetadata.songName = '';
}
};
inputSongArtist.onChange = function(event:UIEvent) {
var valid:Bool = event.target.text != null && event.target.text != '';
if (valid)
{
inputSongArtist.removeClass('invalid-value');
state.currentSongMetadata.artist = event.target.text;
}
else
{
state.currentSongMetadata.artist = '';
}
};
inputStage.onChange = function(event:UIEvent) {
var valid:Bool = event.data != null && event.data.id != null;
if (valid)
{
state.currentSongMetadata.playData.stage = event.data.id;
}
};
var startingValueStage = ChartEditorDropdowns.populateDropdownWithStages(inputStage, state.currentSongMetadata.playData.stage);
inputStage.value = startingValueStage;
inputNoteStyle.onChange = function(event:UIEvent) {
if (event.data?.id == null) return;
state.currentSongNoteStyle = event.data.id;
};
inputBPM.onChange = function(event:UIEvent) {
if (event.value == null || event.value <= 0) return;
// Use a command so we can undo/redo this action.
var startingBPM = state.currentSongMetadata.timeChanges[0].bpm;
if (event.value != startingBPM)
{
state.performCommand(new ChangeStartingBPMCommand(event.value));
}
};
inputOffsetInst.onChange = function(event:UIEvent) {
if (event.value == null) return;
state.currentInstrumentalOffset = event.value;
Conductor.instrumentalOffset = event.value;
// Update song length.
state.songLengthInMs = (state.audioInstTrack?.length ?? 1000.0) + Conductor.instrumentalOffset;
};
inputOffsetVocal.onChange = function(event:UIEvent) {
if (event.value == null) return;
state.currentSongMetadata.offsets.setVocalOffset(state.currentSongMetadata.playData.characters.player, event.value);
};
inputScrollSpeed.onChange = function(event:UIEvent) {
var valid:Bool = event.target.value != null && event.target.value > 0;
if (valid)
{
inputScrollSpeed.removeClass('invalid-value');
state.currentSongChartScrollSpeed = event.target.value;
}
else
{
state.currentSongChartScrollSpeed = 1.0;
}
labelScrollSpeed.text = 'Scroll Speed: ${state.currentSongChartScrollSpeed}x';
};
buttonCharacterOpponent.onClick = function(_) {
state.openCharacterDropdown(CharacterType.DAD, false);
};
buttonCharacterGirlfriend.onClick = function(_) {
state.openCharacterDropdown(CharacterType.GF, false);
};
buttonCharacterPlayer.onClick = function(_) {
state.openCharacterDropdown(CharacterType.BF, false);
};
refresh();
}
public override function refresh():Void
{
inputSongName.value = state.currentSongMetadata.songName;
inputSongArtist.value = state.currentSongMetadata.artist;
inputStage.value = state.currentSongMetadata.playData.stage;
inputNoteStyle.value = state.currentSongMetadata.playData.noteStyle;
inputBPM.value = state.currentSongMetadata.timeChanges[0].bpm;
inputScrollSpeed.value = state.currentSongChartScrollSpeed;
labelScrollSpeed.text = 'Scroll Speed: ${state.currentSongChartScrollSpeed}x';
frameVariation.text = 'Variation: ${state.selectedVariation.toTitleCase()}';
frameDifficulty.text = 'Difficulty: ${state.selectedDifficulty.toTitleCase()}';
var stageId:String = state.currentSongMetadata.playData.stage;
var stageData:Null<StageData> = StageDataParser.parseStageData(stageId);
if (inputStage != null)
{
inputStage.value = (stageData != null) ?
{id: stageId, text: stageData.name} :
{id: "mainStage", text: "Main Stage"};
}
var LIMIT = 6;
var charDataOpponent:CharacterData = CharacterDataParser.fetchCharacterData(state.currentSongMetadata.playData.characters.opponent);
buttonCharacterOpponent.icon = CharacterDataParser.getCharPixelIconAsset(state.currentSongMetadata.playData.characters.opponent);
buttonCharacterOpponent.text = charDataOpponent.name.length > LIMIT ? '${charDataOpponent.name.substr(0, LIMIT)}.' : '${charDataOpponent.name}';
var charDataGirlfriend:CharacterData = CharacterDataParser.fetchCharacterData(state.currentSongMetadata.playData.characters.girlfriend);
buttonCharacterGirlfriend.icon = CharacterDataParser.getCharPixelIconAsset(state.currentSongMetadata.playData.characters.girlfriend);
buttonCharacterGirlfriend.text = charDataGirlfriend.name.length > LIMIT ? '${charDataGirlfriend.name.substr(0, LIMIT)}.' : '${charDataGirlfriend.name}';
var charDataPlayer:CharacterData = CharacterDataParser.fetchCharacterData(state.currentSongMetadata.playData.characters.player);
buttonCharacterPlayer.icon = CharacterDataParser.getCharPixelIconAsset(state.currentSongMetadata.playData.characters.player);
buttonCharacterPlayer.text = charDataPlayer.name.length > LIMIT ? '${charDataPlayer.name.substr(0, LIMIT)}.' : '${charDataPlayer.name}';
}
public static function build(state:ChartEditorState):ChartEditorMetadataToolbox
{
return new ChartEditorMetadataToolbox(state);
}
}

View file

@ -175,6 +175,7 @@ class SongMenuItem extends FlxSpriteGroup
trace(char); trace(char);
// TODO: Put this in the character metadata where it belongs. // TODO: Put this in the character metadata where it belongs.
// TODO: Also, can use CharacterDataParser.getCharPixelIconAsset()
switch (char) switch (char)
{ {
case "monster-christmas": case "monster-christmas":