Merge pull request #246 from FunkinCrew/icon-charswitcher

Icon charswitcher
This commit is contained in:
Eric 2023-12-13 22:33:43 -05:00 committed by GitHub
commit 9d9cda9ab1
15 changed files with 564 additions and 273 deletions

2
assets

@ -1 +1 @@
Subproject commit 42b4bee68600bfb9c31831ccdd0579c00930d771
Subproject commit 7d59681870d2b73417a9f5b553720e8b7120bbde

View file

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

View file

@ -280,6 +280,36 @@ class CharacterDataParser
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.
*/

View file

@ -1,6 +1,9 @@
package funkin.ui.debug.charting;
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 flixel.addons.display.FlxSliceSprite;
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.TreeView;
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.Screen;
import haxe.ui.events.DragEvent;
@ -114,6 +119,7 @@ import haxe.ui.events.UIEvent;
import haxe.ui.events.UIEvent;
import haxe.ui.focus.FocusManager;
import openfl.display.BitmapData;
import flixel.input.mouse.FlxMouseEvent;
import flixel.text.FlxText;
using Lambda;
@ -2310,6 +2316,21 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (!Preferences.debugDisplay) menubar.paddingLeft = null;
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);
}
});
}
/**
@ -2350,13 +2371,13 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
else
{
Conductor.currentTimeChange.bpm += 1;
refreshMetadataToolbox();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
}
playbarBPM.onRightClick = _ -> {
Conductor.currentTimeChange.bpm -= 1;
refreshMetadataToolbox();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
// Add functionality to the menu items.
@ -2622,7 +2643,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
* Open the backups folder in the file explorer.
* Don't call this on HTML5.
*/
function openBackupsFolder():Void
function openBackupsFolder(?_):Void
{
#if sys
// TODO: Is there a way to open a folder and highlight a file in it?
@ -3411,6 +3432,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
var overlapsSelection:Bool = FlxG.mouse.overlaps(renderedSelectionSquares);
var overlapsHealthIcons:Bool = FlxG.mouse.overlaps(healthIconBF) || FlxG.mouse.overlaps(healthIconDad);
if (FlxG.mouse.justPressedMiddle)
{
if (scrollAnchorScreenPos == null)
@ -3430,11 +3453,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
scrollAnchorScreenPos = null;
}
else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea))
else if (gridPlayheadScrollArea != null && FlxG.mouse.overlaps(gridPlayheadScrollArea) && !isCursorOverHaxeUI)
{
gridPlayheadScrollAreaPressed = true;
}
else if (notePreview != null && !isCursorOverHaxeUI && FlxG.mouse.overlaps(notePreview))
else if (notePreview != null && FlxG.mouse.overlaps(notePreview) && !isCursorOverHaxeUI)
{
// Clicked note preview
notePreviewScrollAreaStartPos = new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY);
@ -4125,6 +4148,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
targetCursorMode = Cell;
}
}
else if (overlapsHealthIcons)
{
targetCursorMode = Pointer;
}
}
}
@ -4155,7 +4182,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
difficultySelectDirty = false;
// 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;
var treeView:Null<TreeView> = difficultyToolbox.findComponent('difficultyToolboxTree');
@ -4202,7 +4229,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handlePlayerPreviewToolbox():Void
{
// 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;
// TODO: Re-enable the player preview once we figure out the performance issues.
@ -4238,7 +4265,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
function handleOpponentPreviewToolbox():Void
{
// 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;
// TODO: Re-enable the player preview once we figure out the performance issues.
@ -4995,7 +5022,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
Conductor.mapTimeChanges(this.currentSongMetadata.timeChanges);
refreshDifficultyTreeSelection();
refreshMetadataToolbox();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
else
{
@ -5004,7 +5031,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
selectedDifficulty = prevDifficulty;
refreshDifficultyTreeSelection();
refreshMetadataToolbox();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
}
else
@ -5023,7 +5050,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
selectedDifficulty = nextDifficulty;
refreshDifficultyTreeSelection();
refreshMetadataToolbox();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
else
{
@ -5032,7 +5059,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
selectedDifficulty = nextDifficulty;
refreshDifficultyTreeSelection();
refreshMetadataToolbox();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
}
@ -5145,7 +5172,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
if (treeView == null)
{
// 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;
treeView = difficultyToolbox.findComponent('difficultyToolboxTree');
@ -5165,7 +5192,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
{
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;
treeView = difficultyToolbox.findComponent('difficultyToolboxTree');
@ -5209,8 +5236,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
trace('Changing difficulty to "$variation:$difficulty"');
selectedVariation = variation;
selectedDifficulty = difficulty;
// refreshDifficultyTreeSelection(treeView);
refreshMetadataToolbox();
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
// case 'song':
// case 'variation':
@ -5219,82 +5245,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
trace('Selected wrong node type, resetting selection.');
var currentTreeDifficultyNode = getCurrentTreeDifficultyNode(treeView);
if (currentTreeDifficultyNode != null) treeView.selectedNode = currentTreeDifficultyNode;
refreshMetadataToolbox();
}
}
/**
* 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"};
this.refreshToolbox(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
}
}

View file

@ -2,8 +2,12 @@ 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;
// @:nullSafety // TODO: Fix null safety when used with HaxeUI build macros.
@:access(funkin.ui.debug.charting.ChartEditorState)
class ChartEditorBaseDialog extends Dialog
{
@ -25,6 +29,18 @@ class ChartEditorBaseDialog extends Dialog
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.
* Override this to add custom behavior.
@ -54,6 +70,36 @@ class ChartEditorBaseDialog extends Dialog
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 =

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.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"))
class ChartEditorUploadChartDialog extends ChartEditorBaseDialog
{

View file

@ -16,6 +16,7 @@ import funkin.play.song.Song;
import funkin.play.stage.StageData;
import funkin.ui.debug.charting.dialogs.ChartEditorAboutDialog;
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.ChartEditorWelcomeDialog;
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.Dialogs;
import haxe.ui.containers.Form;
import haxe.ui.containers.menus.Menu;
import haxe.ui.containers.VBox;
import haxe.ui.core.Component;
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
{
// Step 1. Song Metadata

View file

@ -99,7 +99,7 @@ class ChartEditorImportExportHandler
state.switchToCurrentInstrumental();
state.postLoadInstrumental();
state.refreshMetadataToolbox();
state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
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.NotificationManager;
import haxe.ui.notifications.NotificationType;
import haxe.ui.notifications.NotificationData.NotificationActionData;
class ChartEditorNotificationHandler
{
@ -77,7 +78,7 @@ class ChartEditorNotificationHandler
* @param actions The actions to add to the notification.
* @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);
}
@ -101,7 +102,8 @@ class ChartEditorNotificationHandler
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);
@ -111,10 +113,10 @@ class ChartEditorNotificationHandler
body: body,
type: type ?? NotificationType.Default,
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.
var actionsContainer:HBox = notif.findComponent('actionsContainer', HBox);
@ -122,13 +124,13 @@ class ChartEditorNotificationHandler
if (Std.isOfType(component, Button))
{
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)
{
button.onClick = function(_) {
// Don't allow actions to be clicked while the playtest is open.
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.data.event.SongEventData;
import funkin.data.song.SongData.SongTimeChange;
import funkin.ui.debug.charting.commands.ChangeStartingBPMCommand;
import funkin.play.character.BaseCharacter.CharacterType;
import funkin.play.character.CharacterData;
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.Dialog.DialogButton;
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.Grid;
import haxe.ui.containers.TreeView;
@ -85,7 +86,8 @@ class ChartEditorToolboxHandler
case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:
onShowToolboxDifficulty(state, toolbox);
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:
onShowToolboxPlayerPreview(state, toolbox);
case ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:
@ -140,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 openRememberedToolboxes(state:ChartEditorState):Void {}
@ -211,7 +229,19 @@ class ChartEditorToolboxHandler
* @param id The asset ID of the toolbox layout.
* @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);
@ -220,7 +250,7 @@ class ChartEditorToolboxHandler
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>
@ -548,180 +578,15 @@ class ChartEditorToolboxHandler
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;
// 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;
}
static function onShowToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void
{
state.refreshMetadataToolbox();
}
static function onHideToolboxMetadata(state:ChartEditorState, toolbox:CollapsibleDialog):Void {}
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);
// TODO: Put this in the character metadata where it belongs.
// TODO: Also, can use CharacterDataParser.getCharPixelIconAsset()
switch (char)
{
case "monster-christmas":