Merge branch 'rewrite/master' into bugfix/chart-editor-notif-in-bg
This commit is contained in:
@@ -19,17 +19,18 @@ import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.misc.VarTween;
|
||||
import flixel.util.FlxColor;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import flixel.util.FlxSort;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.audio.visualize.PolygonSpectogram;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.data.song.SongData.SongCharacterData;
|
||||
import funkin.data.song.SongData.SongChartData;
|
||||
import funkin.data.song.SongData.SongEventData;
|
||||
import funkin.data.song.SongData.SongMetadata;
|
||||
import funkin.data.song.SongData.SongNoteData;
|
||||
import funkin.data.song.SongDataUtils;
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.input.TurboKeyHandler;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
@@ -53,6 +54,7 @@ import funkin.save.Save;
|
||||
import funkin.ui.debug.charting.commands.AddEventsCommand;
|
||||
import funkin.ui.debug.charting.commands.AddNotesCommand;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.commands.ChartEditorCommand;
|
||||
import funkin.ui.debug.charting.commands.CutItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.DeselectAllItemsCommand;
|
||||
import funkin.ui.debug.charting.commands.DeselectItemsCommand;
|
||||
@@ -73,15 +75,21 @@ import funkin.ui.debug.charting.components.ChartEditorEventSprite;
|
||||
import funkin.ui.debug.charting.components.ChartEditorHoldNoteSprite;
|
||||
import funkin.ui.debug.charting.components.ChartEditorNotePreview;
|
||||
import funkin.ui.debug.charting.components.ChartEditorNoteSprite;
|
||||
import funkin.ui.debug.charting.components.ChartEditorPlaybarHead;
|
||||
import funkin.ui.debug.charting.components.ChartEditorSelectionSquareSprite;
|
||||
import funkin.ui.debug.charting.handlers.ChartEditorShortcutHandler;
|
||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||
import funkin.ui.haxeui.HaxeUIState;
|
||||
import funkin.ui.mainmenu.MainMenuState;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import haxe.DynamicAccess;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.backend.flixel.UIRuntimeState;
|
||||
import haxe.ui.backend.flixel.UIState;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
@@ -90,16 +98,18 @@ import haxe.ui.components.TextField;
|
||||
import haxe.ui.containers.dialogs.CollapsibleDialog;
|
||||
import haxe.ui.containers.Frame;
|
||||
import haxe.ui.containers.menus.Menu;
|
||||
import haxe.ui.containers.menus.MenuBar;
|
||||
import haxe.ui.containers.menus.MenuItem;
|
||||
import haxe.ui.containers.TreeView;
|
||||
import haxe.ui.containers.TreeViewNode;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.DragEvent;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.focus.FocusManager;
|
||||
import openfl.display.BitmapData;
|
||||
import funkin.util.FileUtil;
|
||||
|
||||
using Lambda;
|
||||
|
||||
@@ -107,24 +117,22 @@ using Lambda;
|
||||
* A state dedicated to allowing the user to create and edit song charts.
|
||||
* Built with HaxeUI for use by both developers and modders.
|
||||
*
|
||||
* Some functionality is moved to other classes to help maintain my sanity.
|
||||
* Some functionality is split into handler classes to help maintain my sanity.
|
||||
*
|
||||
* @author MasterEric
|
||||
*/
|
||||
@:nullSafety
|
||||
class ChartEditorState extends HaxeUIState
|
||||
// @:nullSafety
|
||||
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/main-view.xml"))
|
||||
class ChartEditorState extends UIState // UIState derives from MusicBeatState
|
||||
{
|
||||
/**
|
||||
* CONSTANTS
|
||||
*/
|
||||
// ==============================
|
||||
// XML Layouts
|
||||
public static final CHART_EDITOR_LAYOUT:String = Paths.ui('chart-editor/main-view');
|
||||
|
||||
public static final CHART_EDITOR_NOTIFBAR_LAYOUT:String = Paths.ui('chart-editor/components/notifbar');
|
||||
public static final CHART_EDITOR_PLAYBARHEAD_LAYOUT:String = Paths.ui('chart-editor/components/playbar-head');
|
||||
|
||||
// Layouts
|
||||
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_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata');
|
||||
public static final CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT:String = Paths.ui('chart-editor/toolbox/difficulty');
|
||||
@@ -866,7 +874,8 @@ class ChartEditorState extends HaxeUIState
|
||||
// ==============================
|
||||
|
||||
/**
|
||||
* The chill audio track that plays when you open the Chart Editor.
|
||||
* The chill audio track that plays in the chart editor.
|
||||
* Plays when the main music is NOT being played.
|
||||
*/
|
||||
var welcomeMusic:FlxSound = new FlxSound();
|
||||
|
||||
@@ -1209,6 +1218,110 @@ class ChartEditorState extends HaxeUIState
|
||||
return currentSongMetadata.playData.characters.instrumental = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* HAXEUI COMPONENTS
|
||||
*/
|
||||
// ==============================
|
||||
|
||||
/**
|
||||
* The layout containing the playbar.
|
||||
* Constructed manually and added to the layout so we can control its position.
|
||||
*/
|
||||
var playbarHeadLayout:Null<ChartEditorPlaybarHead> = null;
|
||||
|
||||
// NOTE: All the components below are automatically assigned via HaxeUI macros.
|
||||
/**
|
||||
* The menubar at the top of the screen.
|
||||
*/
|
||||
// var menubar:MenuBar;
|
||||
/**
|
||||
* The `File -> New Chart` menu item.
|
||||
*/
|
||||
// var menubarItemNewChart:MenuItem;
|
||||
/**
|
||||
* The `File -> Open Chart` menu item.
|
||||
*/
|
||||
// var menubarItemOpenChart:MenuItem;
|
||||
/**
|
||||
* The `File -> Open Recent` menu.
|
||||
*/
|
||||
// var menubarOpenRecent:Menu;
|
||||
/**
|
||||
* The `File -> Save Chart` menu item.
|
||||
*/
|
||||
// var menubarItemSaveChart:MenuItem;
|
||||
/**
|
||||
* The `File -> Save Chart As` menu item.
|
||||
*/
|
||||
// var menubarItemSaveChartAs:MenuItem;
|
||||
/**
|
||||
* The `File -> Preferences` menu item.
|
||||
*/
|
||||
// var menubarItemPreferences:MenuItem;
|
||||
/**
|
||||
* The `File -> Exit` menu item.
|
||||
*/
|
||||
// var menubarItemExit:MenuItem;
|
||||
/**
|
||||
* The `Edit -> Undo` menu item.
|
||||
*/
|
||||
// var menubarItemUndo:MenuItem;
|
||||
/**
|
||||
* The `Edit -> Redo` menu item.
|
||||
*/
|
||||
// var menubarItemRedo:MenuItem;
|
||||
/**
|
||||
* The `Edit -> Cut` menu item.
|
||||
*/
|
||||
// var menubarItemCut:MenuItem;
|
||||
/**
|
||||
* The `Edit -> Copy` menu item.
|
||||
*/
|
||||
// var menubarItemCopy:MenuItem;
|
||||
/**
|
||||
* The `Edit -> Paste` menu item.
|
||||
*/
|
||||
// var menubarItemPaste:MenuItem;
|
||||
/**
|
||||
* The `Edit -> Paste Unsnapped` menu item.
|
||||
*/
|
||||
// var menubarItemPasteUnsnapped:MenuItem;
|
||||
/**
|
||||
* The `Edit -> Delete` menu item.
|
||||
*/
|
||||
// var menubarItemDelete:MenuItem;
|
||||
/**
|
||||
* The label by the playbar telling the song position.
|
||||
*/
|
||||
// var playbarSongPos:Label;
|
||||
/**
|
||||
* The label by the playbar telling the song time remaining.
|
||||
*/
|
||||
// var playbarSongRemaining:Label;
|
||||
/**
|
||||
* The label by the playbar telling the note snap.
|
||||
*/
|
||||
// var playbarNoteSnap:Label;
|
||||
/**
|
||||
* The button by the playbar to jump to the start of the song.
|
||||
*/
|
||||
// var playbarStart:Button;
|
||||
/**
|
||||
* The button by the playbar to jump backwards in the song.
|
||||
*/
|
||||
// var playbarBack:Button;
|
||||
/**
|
||||
* The button by the playbar to play or pause the song.
|
||||
*/
|
||||
// var playbarPlay:Button;
|
||||
/**
|
||||
* The button by the playbar to jump forwards in the song.
|
||||
*/
|
||||
// var playbarForward:Button;
|
||||
/**
|
||||
* The button by the playbar to jump to the end of the song.
|
||||
*/
|
||||
// var playbarEnd:Button;
|
||||
/**
|
||||
* RENDER OBJECTS
|
||||
*/
|
||||
@@ -1298,41 +1411,6 @@ class ChartEditorState extends HaxeUIState
|
||||
*/
|
||||
var menuBG:Null<FlxSprite> = null;
|
||||
|
||||
/**
|
||||
* The layout containing the playbar head slider.
|
||||
*/
|
||||
var playbarHeadLayout:Null<Component> = null;
|
||||
|
||||
/**
|
||||
* The submenu in the menubar containing recently opened files.
|
||||
*/
|
||||
var menubarOpenRecent:Null<Menu> = null;
|
||||
|
||||
/**
|
||||
* The item in the menubar to save the currently opened chart.
|
||||
*/
|
||||
var menubarItemSaveChart:Null<MenuItem> = null;
|
||||
|
||||
/**
|
||||
* The playbar head slider.
|
||||
*/
|
||||
var playbarHead:Null<Slider> = null;
|
||||
|
||||
/**
|
||||
* The label by the playbar telling the song position.
|
||||
*/
|
||||
var playbarSongPos:Null<Label> = null;
|
||||
|
||||
/**
|
||||
* The label by the playbar telling the song time remaining.
|
||||
*/
|
||||
var playbarSongRemaining:Null<Label> = null;
|
||||
|
||||
/**
|
||||
* The label by the playbar telling the note snap.
|
||||
*/
|
||||
var playbarNoteSnap:Null<Label> = null;
|
||||
|
||||
/**
|
||||
* The sprite group containing the note graphics.
|
||||
* Only displays a subset of the data from `currentSongChartNoteData`,
|
||||
@@ -1432,9 +1510,7 @@ class ChartEditorState extends HaxeUIState
|
||||
|
||||
public function new(?params:ChartEditorParams)
|
||||
{
|
||||
// Load the HaxeUI XML file.
|
||||
super(CHART_EDITOR_LAYOUT);
|
||||
|
||||
super();
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@@ -1481,7 +1557,7 @@ class ChartEditorState extends HaxeUIState
|
||||
// super.create() must be called first, the HaxeUI components get created here.
|
||||
super.create();
|
||||
// Set the z-index of the HaxeUI.
|
||||
this.component.zIndex = 100;
|
||||
this.root.zIndex = 100;
|
||||
|
||||
// Show the mouse cursor.
|
||||
Cursor.show();
|
||||
@@ -1562,6 +1638,7 @@ class ChartEditorState extends HaxeUIState
|
||||
function setupWelcomeMusic()
|
||||
{
|
||||
this.welcomeMusic.loadEmbedded(Paths.music('chartEditorLoop/chartEditorLoop'));
|
||||
FlxG.sound.list.add(this.welcomeMusic);
|
||||
this.welcomeMusic.looped = true;
|
||||
}
|
||||
|
||||
@@ -1633,8 +1710,6 @@ class ChartEditorState extends HaxeUIState
|
||||
var menuItemRecentChart:MenuItem = new MenuItem();
|
||||
menuItemRecentChart.text = chartPath;
|
||||
menuItemRecentChart.onClick = function(_event) {
|
||||
stopWelcomeMusic();
|
||||
|
||||
// Load chart from file
|
||||
var result:Null<Array<String>> = this.loadFromFNFCPath(chartPath);
|
||||
if (result != null)
|
||||
@@ -1671,14 +1746,20 @@ class ChartEditorState extends HaxeUIState
|
||||
#end
|
||||
}
|
||||
|
||||
function fadeInWelcomeMusic():Void
|
||||
var bgMusicTimer:FlxTimer;
|
||||
|
||||
function fadeInWelcomeMusic(?extraWait:Float = 0, ?fadeInTime:Float = 5):Void
|
||||
{
|
||||
this.welcomeMusic.play();
|
||||
this.welcomeMusic.fadeIn(4, 0, 1.0);
|
||||
bgMusicTimer = new FlxTimer().start(extraWait, (_) -> {
|
||||
this.welcomeMusic.volume = 0;
|
||||
this.welcomeMusic.play();
|
||||
this.welcomeMusic.fadeIn(fadeInTime, 0, 1.0);
|
||||
});
|
||||
}
|
||||
|
||||
function stopWelcomeMusic():Void
|
||||
{
|
||||
if (bgMusicTimer != null) bgMusicTimer.cancel();
|
||||
// this.welcomeMusic.fadeOut(4, 0);
|
||||
this.welcomeMusic.pause();
|
||||
}
|
||||
@@ -1926,25 +2007,20 @@ class ChartEditorState extends HaxeUIState
|
||||
|
||||
function buildAdditionalUI():Void
|
||||
{
|
||||
playbarHeadLayout = buildComponent(CHART_EDITOR_PLAYBARHEAD_LAYOUT);
|
||||
|
||||
if (playbarHeadLayout == null) throw 'ERROR: Failed to construct playbarHeadLayout! Check "${CHART_EDITOR_PLAYBARHEAD_LAYOUT}".';
|
||||
playbarHeadLayout = new ChartEditorPlaybarHead();
|
||||
|
||||
playbarHeadLayout.zIndex = 110;
|
||||
|
||||
playbarHeadLayout.width = FlxG.width - 8;
|
||||
playbarHeadLayout.height = 10;
|
||||
playbarHeadLayout.x = 4;
|
||||
playbarHeadLayout.y = FlxG.height - 48 - 8;
|
||||
|
||||
playbarHead = playbarHeadLayout.findComponent('playbarHead', Slider);
|
||||
if (playbarHead == null) throw 'ERROR: Failed to fetch playbarHead from playbarHeadLayout! Check "${CHART_EDITOR_PLAYBARHEAD_LAYOUT}".';
|
||||
playbarHead.allowFocus = false;
|
||||
playbarHead.width = FlxG.width;
|
||||
playbarHead.height = 10;
|
||||
playbarHead.styleString = 'padding-left: 0px; padding-right: 0px; border-left: 0px; border-right: 0px;';
|
||||
playbarHeadLayout.playbarHead.allowFocus = false;
|
||||
playbarHeadLayout.playbarHead.width = FlxG.width;
|
||||
playbarHeadLayout.playbarHead.height = 10;
|
||||
playbarHeadLayout.playbarHead.styleString = 'padding-left: 0px; padding-right: 0px; border-left: 0px; border-right: 0px;';
|
||||
|
||||
playbarHead.onDragStart = function(_:DragEvent) {
|
||||
playbarHeadLayout.playbarHead.onDragStart = function(_:DragEvent) {
|
||||
playbarHeadDragging = true;
|
||||
|
||||
// If we were dragging the playhead while the song was playing, resume playing.
|
||||
@@ -1959,10 +2035,10 @@ class ChartEditorState extends HaxeUIState
|
||||
}
|
||||
}
|
||||
|
||||
playbarHead.onDrag = function(_:DragEvent) {
|
||||
playbarHeadLayout.playbarHead.onDrag = function(_:DragEvent) {
|
||||
if (playbarHeadDragging)
|
||||
{
|
||||
var value:Null<Float> = playbarHead?.value;
|
||||
var value:Null<Float> = playbarHeadLayout.playbarHead?.value;
|
||||
|
||||
// Set the song position to where the playhead was moved to.
|
||||
scrollPositionInPixels = songLengthInPixels * ((value ?? 0.0) / 100);
|
||||
@@ -1971,7 +2047,7 @@ class ChartEditorState extends HaxeUIState
|
||||
}
|
||||
}
|
||||
|
||||
playbarHead.onDragEnd = function(_:DragEvent) {
|
||||
playbarHeadLayout.playbarHead.onDragEnd = function(_:DragEvent) {
|
||||
playbarHeadDragging = false;
|
||||
|
||||
// If we were dragging the playhead while the song was playing, resume playing.
|
||||
@@ -1985,14 +2061,6 @@ class ChartEditorState extends HaxeUIState
|
||||
|
||||
add(playbarHeadLayout);
|
||||
|
||||
menubarOpenRecent = findComponent('menubarOpenRecent', Menu);
|
||||
if (menubarOpenRecent == null) throw "Could not find menubarOpenRecent!";
|
||||
|
||||
menubarItemSaveChart = findComponent('menubarItemSaveChart', MenuItem);
|
||||
if (menubarItemSaveChart == null) throw "Could not find menubarItemSaveChart!";
|
||||
|
||||
var menubar = findComponent('menubar', MenuBar);
|
||||
if (menubar == null) throw "Could not find menubar!";
|
||||
if (!Preferences.debugDisplay) menubar.paddingLeft = null;
|
||||
|
||||
this.setupNotifications();
|
||||
@@ -2005,27 +2073,52 @@ class ChartEditorState extends HaxeUIState
|
||||
{
|
||||
// Add functionality to the playbar.
|
||||
|
||||
addUIClickListener('playbarPlay', _ -> toggleAudioPlayback());
|
||||
addUIClickListener('playbarStart', _ -> playbarButtonPressed = 'playbarStart');
|
||||
addUIClickListener('playbarBack', _ -> playbarButtonPressed = 'playbarBack');
|
||||
addUIClickListener('playbarForward', _ -> playbarButtonPressed = 'playbarForward');
|
||||
addUIClickListener('playbarEnd', _ -> playbarButtonPressed = 'playbarEnd');
|
||||
playbarStart.onClick = _ -> playbarButtonPressed = 'playbarStart';
|
||||
playbarBack.onClick = _ -> playbarButtonPressed = 'playbarBack';
|
||||
playbarPlay.onClick = _ -> toggleAudioPlayback();
|
||||
playbarForward.onClick = _ -> playbarButtonPressed = 'playbarForward';
|
||||
playbarEnd.onClick = _ -> playbarButtonPressed = 'playbarEnd';
|
||||
|
||||
// Cycle note snap quant.
|
||||
addUIRightClickListener('playbarNoteSnap', function(_) {
|
||||
playbarNoteSnap.onRightClick = _ -> {
|
||||
noteSnapQuantIndex--;
|
||||
if (noteSnapQuantIndex < 0) noteSnapQuantIndex = SNAP_QUANTS.length - 1;
|
||||
});
|
||||
addUIClickListener('playbarNoteSnap', function(_) {
|
||||
noteSnapQuantIndex++;
|
||||
if (noteSnapQuantIndex >= SNAP_QUANTS.length) noteSnapQuantIndex = 0;
|
||||
});
|
||||
};
|
||||
playbarNoteSnap.onClick = _ -> {
|
||||
if (FlxG.keys.pressed.SHIFT)
|
||||
{
|
||||
noteSnapQuantIndex = BASE_QUANT_INDEX;
|
||||
}
|
||||
else
|
||||
{
|
||||
noteSnapQuantIndex++;
|
||||
if (noteSnapQuantIndex >= SNAP_QUANTS.length) noteSnapQuantIndex = 0;
|
||||
}
|
||||
};
|
||||
|
||||
playbarBPM.onClick = _ -> {
|
||||
if (FlxG.keys.pressed.CONTROL)
|
||||
{
|
||||
this.setToolboxState(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Conductor.currentTimeChange.bpm += 1;
|
||||
refreshMetadataToolbox();
|
||||
}
|
||||
}
|
||||
|
||||
playbarBPM.onRightClick = _ -> {
|
||||
Conductor.currentTimeChange.bpm -= 1;
|
||||
refreshMetadataToolbox();
|
||||
}
|
||||
|
||||
// Add functionality to the menu items.
|
||||
|
||||
addUIClickListener('menubarItemNewChart', _ -> this.openWelcomeDialog(true));
|
||||
addUIClickListener('menubarItemOpenChart', _ -> this.openBrowseFNFC(true));
|
||||
addUIClickListener('menubarItemSaveChart', _ -> {
|
||||
// File
|
||||
menubarItemNewChart.onClick = _ -> this.openWelcomeDialog(true);
|
||||
menubarItemOpenChart.onClick = _ -> this.openBrowseFNFC(true);
|
||||
menubarItemSaveChart.onClick = _ -> {
|
||||
if (currentWorkingFilePath != null)
|
||||
{
|
||||
this.exportAllSongData(true, currentWorkingFilePath);
|
||||
@@ -2034,17 +2127,14 @@ class ChartEditorState extends HaxeUIState
|
||||
{
|
||||
this.exportAllSongData(false, null);
|
||||
}
|
||||
});
|
||||
addUIClickListener('menubarItemSaveChartAs', _ -> this.exportAllSongData(false, null));
|
||||
addUIClickListener('menubarItemLoadInst', _ -> this.openUploadInstDialog(true));
|
||||
addUIClickListener('menubarItemImportChart', _ -> this.openImportChartDialog('legacy', true));
|
||||
addUIClickListener('menubarItemExit', _ -> quitChartEditor());
|
||||
};
|
||||
menubarItemSaveChartAs.onClick = _ -> this.exportAllSongData(false, null);
|
||||
menubarItemExit.onClick = _ -> quitChartEditor();
|
||||
|
||||
addUIClickListener('menubarItemUndo', _ -> undoLastCommand());
|
||||
|
||||
addUIClickListener('menubarItemRedo', _ -> redoLastCommand());
|
||||
|
||||
addUIClickListener('menubarItemCopy', function(_) {
|
||||
// Edit
|
||||
menubarItemUndo.onClick = _ -> undoLastCommand();
|
||||
menubarItemRedo.onClick = _ -> redoLastCommand();
|
||||
menubarItemCopy.onClick = function(_) {
|
||||
// Doesn't use a command because it's not undoable.
|
||||
|
||||
// Calculate a single time offset for all the notes and events.
|
||||
@@ -2062,24 +2152,23 @@ class ChartEditorState extends HaxeUIState
|
||||
notes: SongDataUtils.buildNoteClipboard(currentNoteSelection, timeOffset),
|
||||
events: SongDataUtils.buildEventClipboard(currentEventSelection, timeOffset),
|
||||
});
|
||||
});
|
||||
};
|
||||
menubarItemCut.onClick = _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
|
||||
addUIClickListener('menubarItemCut', _ -> performCommand(new CutItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
|
||||
addUIClickListener('menubarItemPaste', _ -> {
|
||||
menubarItemPaste.onClick = _ -> {
|
||||
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
|
||||
var targetStep:Float = Conductor.getTimeInSteps(targetMs);
|
||||
var targetSnappedStep:Float = Math.floor(targetStep / noteSnapRatio) * noteSnapRatio;
|
||||
var targetSnappedMs:Float = Conductor.getStepTimeInMs(targetSnappedStep);
|
||||
performCommand(new PasteItemsCommand(targetSnappedMs));
|
||||
});
|
||||
};
|
||||
|
||||
addUIClickListener('menubarItemPasteUnsnapped', _ -> {
|
||||
menubarItemPasteUnsnapped.onClick = _ -> {
|
||||
var targetMs:Float = scrollPositionInMs + playheadPositionInMs;
|
||||
performCommand(new PasteItemsCommand(targetMs));
|
||||
});
|
||||
};
|
||||
|
||||
addUIClickListener('menubarItemDelete', function(_) {
|
||||
menubarItemDelete.onClick = _ -> {
|
||||
if (currentNoteSelection.length > 0 && currentEventSelection.length > 0)
|
||||
{
|
||||
performCommand(new RemoveItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
@@ -2096,128 +2185,120 @@ class ChartEditorState extends HaxeUIState
|
||||
{
|
||||
// Do nothing???
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
addUIClickListener('menubarItemFlipNotes', _ -> performCommand(new FlipNotesCommand(currentNoteSelection)));
|
||||
menubarItemFlipNotes.onClick = _ -> performCommand(new FlipNotesCommand(currentNoteSelection));
|
||||
|
||||
addUIClickListener('menubarItemSelectAll', _ -> performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
menubarItemSelectAll.onClick = _ -> performCommand(new SelectAllItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
|
||||
addUIClickListener('menubarItemSelectInverse', _ -> performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
menubarItemSelectInverse.onClick = _ -> performCommand(new InvertSelectedItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
|
||||
addUIClickListener('menubarItemSelectNone', _ -> performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection)));
|
||||
menubarItemSelectNone.onClick = _ -> performCommand(new DeselectAllItemsCommand(currentNoteSelection, currentEventSelection));
|
||||
|
||||
addUIClickListener('menubarItemPlaytestFull', _ -> testSongInPlayState(false));
|
||||
addUIClickListener('menubarItemPlaytestMinimal', _ -> testSongInPlayState(true));
|
||||
menubarItemPlaytestFull.onClick = _ -> testSongInPlayState(false);
|
||||
menubarItemPlaytestMinimal.onClick = _ -> testSongInPlayState(true);
|
||||
|
||||
addUIClickListener('menuBarItemNoteSnapDecrease', _ -> {
|
||||
menuBarItemNoteSnapDecrease.onClick = _ -> {
|
||||
noteSnapQuantIndex--;
|
||||
if (noteSnapQuantIndex < 0) noteSnapQuantIndex = SNAP_QUANTS.length - 1;
|
||||
});
|
||||
addUIClickListener('menuBarItemNoteSnapIncrease', _ -> {
|
||||
};
|
||||
menuBarItemNoteSnapIncrease.onClick = _ -> {
|
||||
noteSnapQuantIndex++;
|
||||
if (noteSnapQuantIndex >= SNAP_QUANTS.length) noteSnapQuantIndex = 0;
|
||||
});
|
||||
};
|
||||
|
||||
addUIChangeListener('menuBarItemInputStyleNone', function(event:UIEvent) {
|
||||
menuBarItemInputStyleNone.onClick = function(event:UIEvent) {
|
||||
currentLiveInputStyle = None;
|
||||
});
|
||||
addUIChangeListener('menuBarItemInputStyleNumberKeys', function(event:UIEvent) {
|
||||
};
|
||||
menuBarItemInputStyleNumberKeys.onClick = function(event:UIEvent) {
|
||||
currentLiveInputStyle = NumberKeys;
|
||||
});
|
||||
addUIChangeListener('menuBarItemInputStyleWASD', function(event:UIEvent) {
|
||||
};
|
||||
menuBarItemInputStyleWASD.onClick = function(event:UIEvent) {
|
||||
currentLiveInputStyle = WASD;
|
||||
});
|
||||
};
|
||||
|
||||
addUIClickListener('menubarItemAbout', _ -> this.openAboutDialog());
|
||||
addUIClickListener('menubarItemWelcomeDialog', _ -> this.openWelcomeDialog(true));
|
||||
menubarItemAbout.onClick = _ -> this.openAboutDialog();
|
||||
menubarItemWelcomeDialog.onClick = _ -> this.openWelcomeDialog(true);
|
||||
|
||||
#if sys
|
||||
addUIClickListener('menubarItemGoToBackupsFolder', _ -> this.openBackupsFolder());
|
||||
menubarItemGoToBackupsFolder.onClick = _ -> this.openBackupsFolder();
|
||||
#else
|
||||
// Disable the menu item if we're not on a desktop platform.
|
||||
var menubarItemGoToBackupsFolder = findComponent('menubarItemGoToBackupsFolder', MenuItem);
|
||||
if (menubarItemGoToBackupsFolder != null) menubarItemGoToBackupsFolder.disabled = true;
|
||||
#end
|
||||
|
||||
addUIClickListener('menubarItemUserGuide', _ -> this.openUserGuideDialog());
|
||||
menubarItemUserGuide.onClick = _ -> this.openUserGuideDialog();
|
||||
|
||||
addUIChangeListener('menubarItemDownscroll', event -> isViewDownscroll = event.value);
|
||||
setUICheckboxSelected('menubarItemDownscroll', isViewDownscroll);
|
||||
menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value;
|
||||
menubarItemDownscroll.selected = isViewDownscroll;
|
||||
|
||||
addUIClickListener('menubarItemDifficultyUp', _ -> incrementDifficulty(1));
|
||||
addUIClickListener('menubarItemDifficultyDown', _ -> incrementDifficulty(-1));
|
||||
menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1);
|
||||
menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1);
|
||||
|
||||
addUIChangeListener('menubarItemPlaytestStartTime', event -> playtestStartTime = event.value);
|
||||
setUICheckboxSelected('menubarItemPlaytestStartTime', playtestStartTime);
|
||||
menubarItemPlaytestStartTime.onChange = event -> playtestStartTime = event.value;
|
||||
menubarItemPlaytestStartTime.selected = playtestStartTime;
|
||||
|
||||
addUIChangeListener('menuBarItemThemeLight', function(event:UIEvent) {
|
||||
menuBarItemThemeLight.onChange = function(event:UIEvent) {
|
||||
if (event.target.value) currentTheme = ChartEditorTheme.Light;
|
||||
});
|
||||
setUICheckboxSelected('menuBarItemThemeLight', currentTheme == ChartEditorTheme.Light);
|
||||
};
|
||||
menuBarItemThemeLight.selected = currentTheme == ChartEditorTheme.Light;
|
||||
|
||||
addUIChangeListener('menuBarItemThemeDark', function(event:UIEvent) {
|
||||
menuBarItemThemeDark.onChange = function(event:UIEvent) {
|
||||
if (event.target.value) currentTheme = ChartEditorTheme.Dark;
|
||||
});
|
||||
setUICheckboxSelected('menuBarItemThemeDark', currentTheme == ChartEditorTheme.Dark);
|
||||
};
|
||||
menuBarItemThemeDark.selected = currentTheme == ChartEditorTheme.Dark;
|
||||
|
||||
addUIClickListener('menubarItemPlayPause', _ -> toggleAudioPlayback());
|
||||
menubarItemPlayPause.onClick = _ -> toggleAudioPlayback();
|
||||
|
||||
addUIClickListener('menubarItemLoadInstrumental', _ -> this.openUploadInstDialog(true));
|
||||
addUIClickListener('menubarItemLoadVocals', _ -> this.openUploadVocalsDialog(true));
|
||||
menubarItemLoadInstrumental.onClick = _ -> this.openUploadInstDialog(true);
|
||||
menubarItemLoadVocals.onClick = _ -> this.openUploadVocalsDialog(true);
|
||||
|
||||
addUIChangeListener('menubarItemMetronomeEnabled', event -> isMetronomeEnabled = event.value);
|
||||
setUICheckboxSelected('menubarItemMetronomeEnabled', isMetronomeEnabled);
|
||||
menubarItemMetronomeEnabled.onChange = event -> isMetronomeEnabled = event.value;
|
||||
menubarItemMetronomeEnabled.selected = isMetronomeEnabled;
|
||||
|
||||
addUIChangeListener('menubarItemPlayerHitsounds', event -> hitsoundsEnabledPlayer = event.value);
|
||||
setUICheckboxSelected('menubarItemPlayerHitsounds', hitsoundsEnabledPlayer);
|
||||
menubarItemPlayerHitsounds.onChange = event -> hitsoundsEnabledPlayer = event.value;
|
||||
menubarItemPlayerHitsounds.selected = hitsoundsEnabledPlayer;
|
||||
|
||||
addUIChangeListener('menubarItemOpponentHitsounds', event -> hitsoundsEnabledOpponent = event.value);
|
||||
setUICheckboxSelected('menubarItemOpponentHitsounds', hitsoundsEnabledOpponent);
|
||||
menubarItemOpponentHitsounds.onChange = event -> hitsoundsEnabledOpponent = event.value;
|
||||
menubarItemOpponentHitsounds.selected = hitsoundsEnabledOpponent;
|
||||
|
||||
var instVolumeLabel:Null<Label> = findComponent('menubarLabelVolumeInstrumental', Label);
|
||||
if (instVolumeLabel != null)
|
||||
{
|
||||
addUIChangeListener('menubarItemVolumeInstrumental', function(event:UIEvent) {
|
||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
||||
instVolumeLabel.text = 'Instrumental - ${Std.int(event.value)}%';
|
||||
});
|
||||
menubarItemVolumeInstrumental.onChange = event -> {
|
||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
||||
menubarLabelVolumeInstrumental.text = 'Instrumental - ${Std.int(event.value)}%';
|
||||
};
|
||||
|
||||
menubarItemVolumeVocals.onChange = event -> {
|
||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume;
|
||||
menubarLabelVolumeVocals.text = 'Voices - ${Std.int(event.value)}%';
|
||||
}
|
||||
|
||||
var vocalsVolumeLabel:Null<Label> = findComponent('menubarLabelVolumeVocals', Label);
|
||||
if (vocalsVolumeLabel != null)
|
||||
{
|
||||
addUIChangeListener('menubarItemVolumeVocals', function(event:UIEvent) {
|
||||
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume;
|
||||
vocalsVolumeLabel.text = 'Voices - ${Std.int(event.value)}%';
|
||||
});
|
||||
menubarItemPlaybackSpeed.onChange = event -> {
|
||||
var pitch:Float = event.value * 2.0 / 100.0;
|
||||
pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25.
|
||||
#if FLX_PITCH
|
||||
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pitch = pitch;
|
||||
#end
|
||||
var pitchDisplay:Float = Std.int(pitch * 100) / 100; // Round to 2 decimal places.
|
||||
menubarLabelPlaybackSpeed.text = 'Playback Speed - ${pitchDisplay}x';
|
||||
}
|
||||
|
||||
var playbackSpeedLabel:Null<Label> = findComponent('menubarLabelPlaybackSpeed', Label);
|
||||
if (playbackSpeedLabel != null)
|
||||
{
|
||||
addUIChangeListener('menubarItemPlaybackSpeed', function(event:UIEvent) {
|
||||
var pitch:Float = event.value * 2.0 / 100.0;
|
||||
pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25.
|
||||
#if FLX_PITCH
|
||||
if (audioInstTrack != null) audioInstTrack.pitch = pitch;
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pitch = pitch;
|
||||
#end
|
||||
var pitchDisplay:Float = Std.int(pitch * 100) / 100; // Round to 2 decimal places.
|
||||
playbackSpeedLabel.text = 'Playback Speed - ${pitchDisplay}x';
|
||||
});
|
||||
playbarDifficulty.onClick = _ -> {
|
||||
this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, true);
|
||||
}
|
||||
|
||||
addUIChangeListener('menubarItemToggleToolboxDifficulty', event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxMetadata', event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxNotes', event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxEvents', event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxPlayerPreview', event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT, event.value));
|
||||
addUIChangeListener('menubarItemToggleToolboxOpponentPreview', event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT, event.value));
|
||||
menubarItemToggleToolboxDifficulty.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_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);
|
||||
menubarItemToggleToolboxEvents.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENTDATA_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);
|
||||
|
||||
// TODO: Pass specific HaxeUI components to add context menus to them.
|
||||
registerContextMenu(null, Paths.ui('chart-editor/context/test'));
|
||||
// registerContextMenu(null, Paths.ui('chart-editor/context/test'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2293,6 +2374,8 @@ class ChartEditorState extends HaxeUIState
|
||||
// TODO: Is there a way to open a folder and highlight a file in it?
|
||||
var absoluteBackupsPath:String = Path.join([Sys.getCwd(), ChartEditorImportExportHandler.BACKUPS_PATH]);
|
||||
WindowUtil.openFolder(absoluteBackupsPath);
|
||||
#else
|
||||
trace('No file system access, cannot open backups folder.');
|
||||
#end
|
||||
}
|
||||
|
||||
@@ -3894,7 +3977,6 @@ class ChartEditorState extends HaxeUIState
|
||||
function handlePlaybar():Void
|
||||
{
|
||||
if (playbarHeadLayout == null) throw "ERROR: Tried to handle playbar, but playbarHeadLayout is null!";
|
||||
if (playbarHead == null) throw "ERROR: Tried to handle playbar, but playbarHeadLayout is null!";
|
||||
|
||||
// Make sure the playbar is never nudged out of the correct spot.
|
||||
playbarHeadLayout.x = 4;
|
||||
@@ -3907,26 +3989,24 @@ class ChartEditorState extends HaxeUIState
|
||||
if (!playbarHeadDragging)
|
||||
{
|
||||
var songPosPercent:Float = songPos / songLengthInMs * 100;
|
||||
if (playbarHead.value != songPosPercent) playbarHead.value = songPosPercent;
|
||||
if (playbarHeadLayout.playbarHead.value != songPosPercent) playbarHeadLayout.playbarHead.value = songPosPercent;
|
||||
}
|
||||
|
||||
var songPosSeconds:String = Std.string(Math.floor((songPos / 1000) % 60)).lpad('0', 2);
|
||||
var songPosMinutes:String = Std.string(Math.floor((songPos / 1000) / 60)).lpad('0', 2);
|
||||
var songPosString:String = '${songPosMinutes}:${songPosSeconds}';
|
||||
|
||||
if (playbarSongPos == null) playbarSongPos = findComponent('playbarSongPos', Label);
|
||||
if (playbarSongPos != null && playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
|
||||
if (playbarSongPos.value != songPosString) playbarSongPos.value = songPosString;
|
||||
|
||||
var songRemainingSeconds:String = Std.string(Math.floor((songRemaining / 1000) % 60)).lpad('0', 2);
|
||||
var songRemainingMinutes:String = Std.string(Math.floor((songRemaining / 1000) / 60)).lpad('0', 2);
|
||||
var songRemainingString:String = '-${songRemainingMinutes}:${songRemainingSeconds}';
|
||||
|
||||
if (playbarSongRemaining == null) playbarSongRemaining = findComponent('playbarSongRemaining', Label);
|
||||
if (playbarSongRemaining != null
|
||||
&& playbarSongRemaining.value != songRemainingString) playbarSongRemaining.value = songRemainingString;
|
||||
if (playbarSongRemaining.value != songRemainingString) playbarSongRemaining.value = songRemainingString;
|
||||
|
||||
if (playbarNoteSnap == null) playbarNoteSnap = findComponent('playbarNoteSnap', Label);
|
||||
if (playbarNoteSnap != null) playbarNoteSnap.text = '1/${noteSnapQuant}';
|
||||
playbarNoteSnap.text = '1/${noteSnapQuant}';
|
||||
playbarDifficulty.text = "Difficulty: " + selectedDifficulty.toTitleCase();
|
||||
playbarBPM.text = "BPM: " + Conductor.currentTimeChange.bpm;
|
||||
}
|
||||
|
||||
function handlePlayhead():Void
|
||||
@@ -4028,18 +4108,18 @@ class ChartEditorState extends HaxeUIState
|
||||
function handleFileKeybinds():Void
|
||||
{
|
||||
// CTRL + N = New Chart
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.N)
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.N && !isHaxeUIDialogOpen)
|
||||
{
|
||||
this.openWelcomeDialog(true);
|
||||
}
|
||||
|
||||
// CTRL + O = Open Chart
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.O)
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.O && !isHaxeUIDialogOpen)
|
||||
{
|
||||
this.openBrowseFNFC(true);
|
||||
}
|
||||
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.S)
|
||||
if (FlxG.keys.pressed.CONTROL && FlxG.keys.justPressed.S && !isHaxeUIDialogOpen)
|
||||
{
|
||||
if (currentWorkingFilePath == null || FlxG.keys.pressed.SHIFT)
|
||||
{
|
||||
@@ -4319,6 +4399,7 @@ class ChartEditorState extends HaxeUIState
|
||||
|
||||
this.persistentUpdate = false;
|
||||
this.persistentDraw = false;
|
||||
stopWelcomeMusic();
|
||||
openSubState(targetState);
|
||||
}
|
||||
|
||||
@@ -4408,7 +4489,7 @@ class ChartEditorState extends HaxeUIState
|
||||
FlxG.camera.focusOn(new FlxPoint(FlxG.width / 2, FlxG.height / 2));
|
||||
FlxG.camera.zoom = 1.0;
|
||||
|
||||
add(this.component);
|
||||
add(this.root);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4424,7 +4505,7 @@ class ChartEditorState extends HaxeUIState
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.play(false, audioInstTrack.time);
|
||||
}
|
||||
|
||||
setComponentText('playbarPlay', '||');
|
||||
playbarPlay.text = '||'; // Pause
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4657,12 +4738,9 @@ class ChartEditorState extends HaxeUIState
|
||||
|
||||
moveSongToScrollPosition();
|
||||
|
||||
var instVolumeSlider:Null<Slider> = findComponent('menubarItemVolumeInstrumental', Slider);
|
||||
var vocalVolumeSlider:Null<Slider> = findComponent('menubarItemVolumeVocals', Slider);
|
||||
|
||||
// Reapply the volume.
|
||||
var instTargetVolume:Float = instVolumeSlider?.value ?? 1.0;
|
||||
var vocalTargetVolume:Float = vocalVolumeSlider?.value ?? 1.0;
|
||||
var instTargetVolume:Float = menubarItemVolumeInstrumental.value ?? 1.0;
|
||||
var vocalTargetVolume:Float = menubarItemVolumeVocals.value ?? 1.0;
|
||||
|
||||
if (audioInstTrack != null)
|
||||
{
|
||||
@@ -4873,48 +4951,30 @@ class ChartEditorState extends HaxeUIState
|
||||
commandHistoryDirty = false;
|
||||
|
||||
// Update the Undo and Redo buttons.
|
||||
var undoButton:Null<MenuItem> = findComponent('menubarItemUndo', MenuItem);
|
||||
|
||||
if (undoButton != null)
|
||||
if (undoHistory.length == 0)
|
||||
{
|
||||
if (undoHistory.length == 0)
|
||||
{
|
||||
// Disable the Undo button.
|
||||
undoButton.disabled = true;
|
||||
undoButton.text = 'Undo';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Change the label to the last command.
|
||||
undoButton.disabled = false;
|
||||
undoButton.text = 'Undo ${undoHistory[undoHistory.length - 1].toString()}';
|
||||
}
|
||||
// Disable the Undo button.
|
||||
menubarItemUndo.disabled = true;
|
||||
menubarItemUndo.text = 'Undo';
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('undoButton is null');
|
||||
// Change the label to the last command.
|
||||
menubarItemUndo.disabled = false;
|
||||
menubarItemUndo.text = 'Undo ${undoHistory[undoHistory.length - 1].toString()}';
|
||||
}
|
||||
|
||||
var redoButton:Null<MenuItem> = findComponent('menubarItemRedo', MenuItem);
|
||||
|
||||
if (redoButton != null)
|
||||
if (redoHistory.length == 0)
|
||||
{
|
||||
if (redoHistory.length == 0)
|
||||
{
|
||||
// Disable the Redo button.
|
||||
redoButton.disabled = true;
|
||||
redoButton.text = 'Redo';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Change the label to the last command.
|
||||
redoButton.disabled = false;
|
||||
redoButton.text = 'Redo ${redoHistory[redoHistory.length - 1].toString()}';
|
||||
}
|
||||
// Disable the Redo button.
|
||||
menubarItemRedo.disabled = true;
|
||||
menubarItemRedo.text = 'Redo';
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('redoButton is null');
|
||||
// Change the label to the last command.
|
||||
menubarItemRedo.disabled = false;
|
||||
menubarItemRedo.text = 'Redo ${redoHistory[redoHistory.length - 1].toString()}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4967,7 +5027,7 @@ class ChartEditorState extends HaxeUIState
|
||||
if (audioInstTrack != null) audioInstTrack.pause();
|
||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.pause();
|
||||
|
||||
setComponentText('playbarPlay', '>');
|
||||
playbarPlay.text = '>';
|
||||
}
|
||||
|
||||
function toggleAudioPlayback():Void
|
||||
@@ -4976,10 +5036,12 @@ class ChartEditorState extends HaxeUIState
|
||||
|
||||
if (audioInstTrack.playing)
|
||||
{
|
||||
fadeInWelcomeMusic(7, 10);
|
||||
stopAudioPlayback();
|
||||
}
|
||||
else
|
||||
{
|
||||
stopWelcomeMusic();
|
||||
startAudioPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package funkin.ui.debug.charting.components;
|
||||
|
||||
import haxe.ui.containers.Box;
|
||||
import haxe.ui.components.HorizontalSlider;
|
||||
|
||||
/**
|
||||
* The component which contains the playhead for the chart editor.
|
||||
* This is in a separate component so it can be positioned independently.
|
||||
*/
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/components/playbar-head.xml"))
|
||||
class ChartEditorPlaybarHead extends Box
|
||||
{
|
||||
// Auto-populated
|
||||
// public var playbarHead:HorizontalSlider;
|
||||
// Auto-populated.
|
||||
// public function new() { }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package funkin.ui.debug.charting.dialogs;
|
||||
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogParams;
|
||||
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/about.xml"))
|
||||
class ChartEditorAboutDialog extends ChartEditorBaseDialog
|
||||
{
|
||||
public function new(state2:ChartEditorState, params2:DialogParams)
|
||||
{
|
||||
super(state2, params2);
|
||||
}
|
||||
|
||||
public static function build(state:ChartEditorState, ?closable:Bool, ?modal:Bool):ChartEditorAboutDialog
|
||||
{
|
||||
var dialog = new ChartEditorAboutDialog(state,
|
||||
{
|
||||
closable: closable ?? true,
|
||||
modal: modal ?? true
|
||||
});
|
||||
|
||||
dialog.showDialog(modal ?? true);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package funkin.ui.debug.charting.dialogs;
|
||||
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogEvent;
|
||||
import haxe.ui.core.Component;
|
||||
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorBaseDialog extends Dialog
|
||||
{
|
||||
var state:ChartEditorState;
|
||||
var params:DialogParams;
|
||||
|
||||
var locked:Bool = false;
|
||||
|
||||
public function new(state:ChartEditorState, params:DialogParams)
|
||||
{
|
||||
super();
|
||||
|
||||
this.state = state;
|
||||
this.params = params;
|
||||
|
||||
this.destroyOnClose = true;
|
||||
this.closable = params.closable ?? false;
|
||||
|
||||
this.onDialogClosed = event -> onClose(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the dialog is closed.
|
||||
* Override this to add custom behavior.
|
||||
*/
|
||||
public function onClose(event:DialogEvent):Void
|
||||
{
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks this dialog from interaction.
|
||||
* Use this when you want to prevent dialog interaction while another dialog is open.
|
||||
*/
|
||||
public function lock():Void
|
||||
{
|
||||
this.locked = true;
|
||||
|
||||
this.closable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks the dialog for interaction.
|
||||
*/
|
||||
public function unlock():Void
|
||||
{
|
||||
this.locked = false;
|
||||
|
||||
this.closable = params.closable ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
typedef DialogParams =
|
||||
{
|
||||
?closable:Bool,
|
||||
?modal:Bool
|
||||
};
|
||||
|
||||
typedef DialogDropTarget =
|
||||
{
|
||||
component:Component,
|
||||
handler:String->Void
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package funkin.ui.debug.charting.dialogs;
|
||||
|
||||
import funkin.input.Cursor;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogDropTarget;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogParams;
|
||||
import funkin.util.FileUtil;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogEvent;
|
||||
import haxe.ui.containers.dialogs.Dialogs;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/upload-chart.xml"))
|
||||
class ChartEditorUploadChartDialog extends ChartEditorBaseDialog
|
||||
{
|
||||
var dropHandlers:Array<DialogDropTarget> = [];
|
||||
|
||||
public function new(state2:ChartEditorState, params2:DialogParams)
|
||||
{
|
||||
super(state2, params2);
|
||||
|
||||
this.dialogCancel.onClick = (_) -> this.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
this.chartBox.onClick = (_) -> this.onClickChartBox();
|
||||
|
||||
this.chartBox.onMouseOver = function(_event) {
|
||||
if (this.locked) return;
|
||||
this.chartBox.swapClass('upload-bg', 'upload-bg-hover');
|
||||
Cursor.cursorMode = Pointer;
|
||||
}
|
||||
|
||||
this.chartBox.onMouseOut = function(_event) {
|
||||
this.chartBox.swapClass('upload-bg-hover', 'upload-bg');
|
||||
Cursor.cursorMode = Default;
|
||||
}
|
||||
|
||||
dropHandlers.push({component: this.chartBox, handler: this.onDropFileChartBox});
|
||||
}
|
||||
|
||||
public static function build(state:ChartEditorState, ?closable:Bool, ?modal:Bool):ChartEditorUploadChartDialog
|
||||
{
|
||||
var dialog = new ChartEditorUploadChartDialog(state,
|
||||
{
|
||||
closable: closable ?? false,
|
||||
modal: modal ?? true
|
||||
});
|
||||
|
||||
for (dropTarget in dialog.dropHandlers)
|
||||
{
|
||||
state.addDropHandler(dropTarget);
|
||||
}
|
||||
|
||||
dialog.showDialog(modal ?? true);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public override function onClose(event:DialogEvent):Void
|
||||
{
|
||||
super.onClose(event);
|
||||
|
||||
if (event.button != DialogButton.APPLY && !this.closable)
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
state.openWelcomeDialog(this.closable);
|
||||
}
|
||||
|
||||
for (dropTarget in dropHandlers)
|
||||
{
|
||||
state.removeDropHandler(dropTarget);
|
||||
}
|
||||
}
|
||||
|
||||
public override function lock():Void
|
||||
{
|
||||
super.lock();
|
||||
this.dialogCancel.disabled = true;
|
||||
}
|
||||
|
||||
public override function unlock():Void
|
||||
{
|
||||
super.unlock();
|
||||
this.dialogCancel.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when clicking the Upload Chart box.
|
||||
*/
|
||||
public function onClickChartBox():Void
|
||||
{
|
||||
if (this.locked) return;
|
||||
|
||||
this.lock();
|
||||
FileUtil.browseForBinaryFile('Open Chart', [FileUtil.FILE_EXTENSION_INFO_FNFC], onSelectFile, onCancelBrowse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a file is selected by dropping a file onto the Upload Chart box.
|
||||
*/
|
||||
function onDropFileChartBox(pathStr:String):Void
|
||||
{
|
||||
var path:Path = new Path(pathStr);
|
||||
trace('Dropped file (${path})');
|
||||
|
||||
try
|
||||
{
|
||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(state, path.toString());
|
||||
if (result != null)
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: result.length == 0 ? 'Loaded chart (${path.toString()})' : 'Loaded chart (${path.toString()})\n${result.join("\n")}',
|
||||
type: result.length == 0 ? NotificationType.Success : NotificationType.Warning,
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
this.hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load chart (${path.toString()})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load chart (${path.toString()}): ${err}',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a file is selected by the dialog displayed when clicking the Upload Chart box.
|
||||
*/
|
||||
function onSelectFile(selectedFile:SelectedFileInfo):Void
|
||||
{
|
||||
this.unlock();
|
||||
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFC(state, selectedFile.bytes);
|
||||
if (result != null)
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded chart (${selectedFile.name})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
|
||||
if (selectedFile.fullPath != null) state.currentWorkingFilePath = selectedFile.fullPath;
|
||||
this.hideDialog(DialogButton.APPLY);
|
||||
}
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load chart (${selectedFile.name}): ${err}',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCancelBrowse():Void
|
||||
{
|
||||
this.unlock();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package funkin.ui.debug.charting.dialogs;
|
||||
|
||||
import funkin.data.song.SongRegistry;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.ui.debug.charting.ChartEditorState;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorBaseDialog.DialogParams;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.SortUtil;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.Link;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogButton;
|
||||
import haxe.ui.containers.dialogs.Dialog.DialogEvent;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog letting the user create a new chart, open a recent chart, or load from a template.
|
||||
* Opens when the chart editor first opens.
|
||||
*/
|
||||
@:build(haxe.ui.ComponentBuilder.build("assets/exclude/data/ui/chart-editor/dialogs/welcome.xml"))
|
||||
@:access(funkin.ui.debug.charting.ChartEditorState)
|
||||
class ChartEditorWelcomeDialog extends ChartEditorBaseDialog
|
||||
{
|
||||
/**
|
||||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @param modal Whether the dialog is locked to the center of the screen (with a dark overlay behind it).
|
||||
*/
|
||||
public function new(state2:ChartEditorState, params2:DialogParams)
|
||||
{
|
||||
super(state2, params2);
|
||||
|
||||
this.splashBrowse.onClick = _ -> onClickButtonBrowse();
|
||||
this.splashCreateFromSongBasicOnly.onClick = _ -> onClickLinkCreateBasicOnly();
|
||||
this.splashCreateFromSongErectOnly.onClick = _ -> onClickLinkCreateErectOnly();
|
||||
this.splashCreateFromSongBasicErect.onClick = _ -> onClickLinkCreateBasicErect();
|
||||
this.splashImportChartLegacy.onClick = _ -> onClickLinkImportChartLegacy();
|
||||
|
||||
// Add items to the Recent Charts list
|
||||
#if sys
|
||||
for (chartPath in state.previousWorkingFilePaths)
|
||||
{
|
||||
if (chartPath == null) continue;
|
||||
this.addRecentFilePath(state, chartPath);
|
||||
}
|
||||
#else
|
||||
this.addHTML5RecentFileMessage();
|
||||
#end
|
||||
|
||||
// Add items to the Load From Template list
|
||||
this.buildTemplateSongList(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param state The current state of the chart editor.
|
||||
* @return A newly created `ChartEditorWelcomeDialog`.
|
||||
*/
|
||||
public static function build(state:ChartEditorState, ?closable:Bool, ?modal:Bool):ChartEditorWelcomeDialog
|
||||
{
|
||||
var dialog = new ChartEditorWelcomeDialog(state,
|
||||
{
|
||||
closable: closable ?? false,
|
||||
modal: modal ?? true
|
||||
});
|
||||
|
||||
dialog.showDialog(modal ?? true);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public override function onClose(event:DialogEvent):Void
|
||||
{
|
||||
super.onClose(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file path to the "Open Recent" scroll box on the left.
|
||||
* @param path
|
||||
*/
|
||||
public function addRecentFilePath(state:ChartEditorState, chartPath:String):Void
|
||||
{
|
||||
var linkRecentChart:Link = new Link();
|
||||
|
||||
var fileNamePattern:EReg = new EReg("([^/\\\\]+)$", "");
|
||||
var fileName:String = fileNamePattern.match(chartPath) ? fileNamePattern.matched(1) : chartPath;
|
||||
linkRecentChart.text = fileName;
|
||||
|
||||
linkRecentChart.tooltip = chartPath;
|
||||
|
||||
#if sys
|
||||
var lastModified:String = "Last Modified: " + sys.FileSystem.stat(chartPath).mtime.toString();
|
||||
linkRecentChart.tooltip += "\n" + lastModified;
|
||||
#end
|
||||
|
||||
linkRecentChart.onClick = function(_event) {
|
||||
this.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Load chart from file
|
||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(state, chartPath);
|
||||
if (result != null)
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: result.length == 0 ? 'Loaded chart (${chartPath.toString()})' : 'Loaded chart (${chartPath.toString()})\n${result.join("\n")}',
|
||||
type: result.length == 0 ? NotificationType.Success : NotificationType.Warning,
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !mac
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load chart (${chartPath.toString()})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: Constants.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
if (!FileUtil.doesFileExist(chartPath))
|
||||
{
|
||||
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
|
||||
linkRecentChart.disabled = true;
|
||||
}
|
||||
|
||||
splashRecentContainer.addComponent(linkRecentChart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a string message to the "Open Recent" scroll box on the left.
|
||||
* Only displays on platforms which don't support direct file system access.
|
||||
*/
|
||||
public function addHTML5RecentFileMessage():Void
|
||||
{
|
||||
var webLoadLabel:Label = new Label();
|
||||
webLoadLabel.text = 'Click the button below to load a chart file (.fnfc) from your computer.';
|
||||
|
||||
splashRecentContainer.addComponent(webLoadLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the links to the "Create From Template" scroll box on the right.
|
||||
*/
|
||||
public function buildTemplateSongList(state:ChartEditorState):Void
|
||||
{
|
||||
var songList:Array<String> = SongRegistry.instance.listEntryIds();
|
||||
songList.sort(SortUtil.alphabetically);
|
||||
|
||||
for (targetSongId in songList)
|
||||
{
|
||||
var songData:Null<Song> = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
if (songData == null) continue;
|
||||
|
||||
var songName:Null<String> = songData.getDifficulty('normal')?.songName;
|
||||
if (songName == null) songName = songData.getDifficulty()?.songName;
|
||||
if (songName == null) // Still null?
|
||||
{
|
||||
trace('[WARN] Could not fetch song name for ${targetSongId}');
|
||||
continue;
|
||||
}
|
||||
|
||||
this.addTemplateSong(songName, targetSongId, (_) -> {
|
||||
this.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Load song from template
|
||||
state.loadSongAsTemplate(targetSongId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param loadTemplateCb The callback to call when the user clicks the link. The callback should load the song ID from the template.
|
||||
*/
|
||||
public function addTemplateSong(songName:String, songId:String, onClickCb:(MouseEvent) -> Void):Void
|
||||
{
|
||||
var linkTemplateSong:Link = new Link();
|
||||
linkTemplateSong.text = songName;
|
||||
linkTemplateSong.onClick = onClickCb;
|
||||
|
||||
this.splashTemplateContainer.addComponent(linkTemplateSong);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the "Browse Chart" button in the dialog.
|
||||
* Reassign this function to change the behavior.
|
||||
*/
|
||||
public function onClickButtonBrowse():Void
|
||||
{
|
||||
// Hide the welcome dialog
|
||||
this.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Open the "Open Chart" dialog
|
||||
state.openBrowseFNFC(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the "Create From Template: Easy/Normal/Hard Only" link in the dialog.
|
||||
* Reassign this function to change the behavior.
|
||||
*/
|
||||
public function onClickLinkCreateBasicOnly():Void
|
||||
{
|
||||
// Hide the welcome dialog
|
||||
this.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
state.openCreateSongWizardBasicOnly(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the "Create From Template: Erect/Nightmare Only" link in the dialog.
|
||||
* Reassign this function to change the behavior.
|
||||
*/
|
||||
public function onClickLinkCreateErectOnly():Void
|
||||
{
|
||||
// Hide the welcome dialog
|
||||
this.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
state.openCreateSongWizardErectOnly(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the "Create From Template: Easy/Normal/Hard/Erect/Nightmare" link in the dialog.
|
||||
* Reassign this function to change the behavior.
|
||||
*/
|
||||
public function onClickLinkCreateBasicErect():Void
|
||||
{
|
||||
// Hide the welcome dialog
|
||||
this.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
state.openCreateSongWizardBasicErect(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the "Import Chart: FNF Legacy" link in the dialog.
|
||||
* Reassign this function to change the behavior.
|
||||
*/
|
||||
public function onClickLinkImportChartLegacy():Void
|
||||
{
|
||||
// Hide the welcome dialog
|
||||
this.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Open the "Import Chart" dialog
|
||||
state.openImportChartWizard('legacy', false);
|
||||
}
|
||||
}
|
||||
@@ -14,13 +14,17 @@ import funkin.play.character.CharacterData;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
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.ChartEditorUploadChartDialog;
|
||||
import funkin.ui.debug.charting.dialogs.ChartEditorWelcomeDialog;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import funkin.util.Constants;
|
||||
import funkin.util.DateUtil;
|
||||
import funkin.util.FileUtil;
|
||||
import funkin.util.SerializerUtil;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.VersionUtil;
|
||||
import funkin.util.DateUtil;
|
||||
import funkin.util.WindowUtil;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.components.Button;
|
||||
@@ -38,6 +42,9 @@ import haxe.ui.containers.Form;
|
||||
import haxe.ui.containers.VBox;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import thx.semver.Version;
|
||||
|
||||
using Lambda;
|
||||
@@ -50,8 +57,6 @@ using Lambda;
|
||||
class ChartEditorDialogHandler
|
||||
{
|
||||
// Paths to HaxeUI layout files for each dialog.
|
||||
static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT:String = Paths.ui('chart-editor/dialogs/about');
|
||||
static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT:String = Paths.ui('chart-editor/dialogs/welcome');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_CHART_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-chart');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-inst');
|
||||
static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata');
|
||||
@@ -72,7 +77,12 @@ class ChartEditorDialogHandler
|
||||
*/
|
||||
public static function openAboutDialog(state:ChartEditorState):Null<Dialog>
|
||||
{
|
||||
return openDialog(state, CHART_EDITOR_DIALOG_ABOUT_LAYOUT, true, true);
|
||||
var dialog = ChartEditorAboutDialog.build(state);
|
||||
|
||||
dialog.zIndex = 1000;
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,177 +93,18 @@ class ChartEditorDialogHandler
|
||||
*/
|
||||
public static function openWelcomeDialog(state:ChartEditorState, closable:Bool = true):Null<Dialog>
|
||||
{
|
||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable);
|
||||
if (dialog == null) throw 'Could not locate Welcome dialog';
|
||||
var dialog = ChartEditorWelcomeDialog.build(state, closable);
|
||||
|
||||
dialog.zIndex = 1000;
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
dialog.onDialogClosed = function(event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
// Called when the Welcome dialog is closed while it is closable.
|
||||
state.stopWelcomeMusic();
|
||||
}
|
||||
|
||||
#if sys
|
||||
var splashRecentContainer:Null<VBox> = dialog.findComponent('splashRecentContainer', VBox);
|
||||
if (splashRecentContainer == null) throw 'Could not locate splashRecentContainer in Welcome dialog';
|
||||
|
||||
for (chartPath in state.previousWorkingFilePaths)
|
||||
{
|
||||
if (chartPath == null) continue;
|
||||
|
||||
var linkRecentChart:Link = new Link();
|
||||
// regex to only use the filename, not the full path
|
||||
// "dadbattle.fnc" insted of "c:/user/docs/funkin/dadbattle.fnc"
|
||||
// hovering tooltip shows full path
|
||||
var fileNamePattern:EReg = new EReg("([^/\\\\]+)$", "");
|
||||
var fileName:String = fileNamePattern.match(chartPath) ? fileNamePattern.matched(1) : chartPath;
|
||||
linkRecentChart.text = fileName;
|
||||
linkRecentChart.tooltip = chartPath;
|
||||
linkRecentChart.onClick = function(_) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
state.stopWelcomeMusic();
|
||||
|
||||
// Load chart from file
|
||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(state, chartPath);
|
||||
if (result != null)
|
||||
{
|
||||
if (result.length == 0)
|
||||
{
|
||||
// No warnings.
|
||||
state.success('Loaded Chart', 'Loaded chart (${chartPath.toString()})');
|
||||
}
|
||||
else
|
||||
{
|
||||
// One or more warnings.
|
||||
state.warning('Loaded Chart', 'Loaded chart (${chartPath.toString()})\n${result.join("\n")}');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state.error('Failed to Load Chart', 'Failed to load chart (${chartPath.toString()})');
|
||||
}
|
||||
}
|
||||
|
||||
if (!FileUtil.doesFileExist(chartPath))
|
||||
{
|
||||
trace('Previously loaded chart file (${chartPath}) does not exist, disabling link...');
|
||||
linkRecentChart.disabled = true;
|
||||
}
|
||||
|
||||
splashRecentContainer.addComponent(linkRecentChart);
|
||||
}
|
||||
#else
|
||||
var splashRecentContainer:Null<VBox> = dialog.findComponent('splashRecentContainer', VBox);
|
||||
if (splashRecentContainer == null) throw 'Could not locate splashRecentContainer in Welcome dialog';
|
||||
|
||||
var webLoadLabel:Label = new Label();
|
||||
webLoadLabel.text = 'Click the button below to load a chart file (.fnfc) from your computer.';
|
||||
|
||||
splashRecentContainer.add(webLoadLabel);
|
||||
#end
|
||||
|
||||
// Create New Song "Easy/Normal/Hard"
|
||||
var linkCreateBasic:Null<Link> = dialog.findComponent('splashCreateFromSongBasicOnly', Link);
|
||||
if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasicOnly link in Welcome dialog';
|
||||
linkCreateBasic.onClick = function(_) {
|
||||
// Hide the welcome dialog
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
state.stopWelcomeMusic();
|
||||
|
||||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
openCreateSongWizardBasicOnly(state, false);
|
||||
}
|
||||
|
||||
// Create New Song "Erect/Nightmare"
|
||||
var linkCreateErect:Null<Link> = dialog.findComponent('splashCreateFromSongErectOnly', Link);
|
||||
if (linkCreateErect == null) throw 'Could not locate splashCreateFromSongErectOnly link in Welcome dialog';
|
||||
linkCreateErect.onClick = function(_) {
|
||||
// Hide the welcome dialog
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
openCreateSongWizardErectOnly(state, false);
|
||||
}
|
||||
|
||||
// Create New Song "Easy/Normal/Hard/Erect/Nightmare"
|
||||
var linkCreateErect:Null<Link> = dialog.findComponent('splashCreateFromSongBasicErect', Link);
|
||||
if (linkCreateErect == null) throw 'Could not locate splashCreateFromSongBasicErect link in Welcome dialog';
|
||||
linkCreateErect.onClick = function(_) {
|
||||
// Hide the welcome dialog
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
openCreateSongWizardBasicErect(state, false);
|
||||
}
|
||||
|
||||
var linkImportChartLegacy:Null<Link> = dialog.findComponent('splashImportChartLegacy', Link);
|
||||
if (linkImportChartLegacy == null) throw 'Could not locate splashImportChartLegacy link in Welcome dialog';
|
||||
linkImportChartLegacy.onClick = function(_) {
|
||||
// Hide the welcome dialog
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
state.stopWelcomeMusic();
|
||||
|
||||
// Open the "Import Chart" dialog
|
||||
openImportChartWizard(state, 'legacy', false);
|
||||
};
|
||||
|
||||
var buttonBrowse:Null<Button> = dialog.findComponent('splashBrowse', Button);
|
||||
if (buttonBrowse == null) throw 'Could not locate splashBrowse button in Welcome dialog';
|
||||
buttonBrowse.onClick = function(_) {
|
||||
// Hide the welcome dialog
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
state.stopWelcomeMusic();
|
||||
|
||||
// Open the "Open Chart" dialog
|
||||
openBrowseFNFC(state, false);
|
||||
}
|
||||
|
||||
var splashTemplateContainer:Null<VBox> = dialog.findComponent('splashTemplateContainer', VBox);
|
||||
if (splashTemplateContainer == null) throw 'Could not locate splashTemplateContainer in Welcome dialog';
|
||||
|
||||
var songList:Array<String> = SongRegistry.instance.listEntryIds();
|
||||
songList.sort(SortUtil.alphabetically);
|
||||
|
||||
for (targetSongId in songList)
|
||||
{
|
||||
var songData:Null<Song> = SongRegistry.instance.fetchEntry(targetSongId);
|
||||
if (songData == null) continue;
|
||||
|
||||
var songName:Null<String> = songData.getDifficulty('normal')?.songName;
|
||||
if (songName == null) songName = songData.getDifficulty()?.songName;
|
||||
if (songName == null) // Still null?
|
||||
{
|
||||
trace('[WARN] Could not fetch song name for ${targetSongId}');
|
||||
continue;
|
||||
}
|
||||
|
||||
var linkTemplateSong:Link = new Link();
|
||||
linkTemplateSong.text = songName;
|
||||
linkTemplateSong.onClick = function(_) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
state.stopWelcomeMusic();
|
||||
|
||||
// Load song from template
|
||||
state.loadSongAsTemplate(targetSongId);
|
||||
}
|
||||
|
||||
splashTemplateContainer.addComponent(linkTemplateSong);
|
||||
}
|
||||
|
||||
state.fadeInWelcomeMusic();
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Description]
|
||||
* @param state
|
||||
* @return Null<Dialog>
|
||||
* Builds and opens a dialog letting the user know a backup is available, and prompting them to load it.
|
||||
*/
|
||||
public static function openBackupAvailableDialog(state:ChartEditorState, welcomeDialog:Null<Dialog>):Null<Dialog>
|
||||
{
|
||||
@@ -333,112 +184,18 @@ class ChartEditorDialogHandler
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog letting the user browse for a chart file to open.
|
||||
* @param state The current chart editor state.
|
||||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static function openBrowseFNFC(state:ChartEditorState, closable:Bool):Null<Dialog>
|
||||
{
|
||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_CHART_LAYOUT, true, closable);
|
||||
if (dialog == null) throw 'Could not locate Upload Chart dialog';
|
||||
|
||||
dialog.onDialogClosed = function(event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (event.button == DialogButton.APPLY)
|
||||
{
|
||||
// Simply let the dialog close.
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
|
||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Upload Chart dialog';
|
||||
var dialog = ChartEditorUploadChartDialog.build(state, closable);
|
||||
|
||||
dialog.zIndex = 1000;
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
buttonCancel.onClick = function(_) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
var chartBox:Null<Box> = dialog.findComponent('chartBox', Box);
|
||||
if (chartBox == null) throw 'Could not locate chartBox in Upload Chart dialog';
|
||||
|
||||
chartBox.onMouseOver = function(_) {
|
||||
chartBox.swapClass('upload-bg', 'upload-bg-hover');
|
||||
Cursor.cursorMode = Pointer;
|
||||
}
|
||||
|
||||
chartBox.onMouseOut = function(_) {
|
||||
chartBox.swapClass('upload-bg-hover', 'upload-bg');
|
||||
Cursor.cursorMode = Default;
|
||||
}
|
||||
|
||||
var onDropFile:String->Void;
|
||||
|
||||
chartBox.onClick = function(_) {
|
||||
Dialogs.openBinaryFile('Open Chart', [
|
||||
{label: 'Friday Night Funkin\' Chart (.fnfc)', extension: 'fnfc'}], function(selectedFile:SelectedFileInfo) {
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFC(state, selectedFile.bytes);
|
||||
if (result != null)
|
||||
{
|
||||
if (result.length == 0)
|
||||
{
|
||||
state.success('Loaded Chart', 'Loaded chart (${selectedFile.name})');
|
||||
}
|
||||
else
|
||||
{
|
||||
state.warning('Loaded Chart', 'Loaded chart (${selectedFile.name})\n${result.join("\n")}');
|
||||
}
|
||||
|
||||
if (selectedFile.fullPath != null) state.currentWorkingFilePath = selectedFile.fullPath;
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
state.error('Failed to Load Chart', 'Failed to load chart (${selectedFile.name}): ${err}');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDropFile = function(pathStr:String) {
|
||||
var path:Path = new Path(pathStr);
|
||||
trace('Dropped file (${path})');
|
||||
|
||||
try
|
||||
{
|
||||
var result:Null<Array<String>> = ChartEditorImportExportHandler.loadFromFNFCPath(state, path.toString());
|
||||
if (result != null)
|
||||
{
|
||||
if (result.length == 0)
|
||||
{
|
||||
state.success('Loaded Chart', 'Loaded chart (${path.file}.${path.ext})');
|
||||
}
|
||||
else
|
||||
{
|
||||
state.warning('Loaded Chart', 'Loaded chart (${path.file}.${path.ext})\n${result.join("\n")}');
|
||||
}
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.error('Failed to Load Chart', 'Failed to load chart (${path.file}.${path.ext})');
|
||||
}
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
state.error('Failed to Load Chart', 'Failed to load chart (${path.file}.${path.ext})');
|
||||
}
|
||||
};
|
||||
|
||||
addDropHandler(chartBox, onDropFile);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
@@ -476,14 +233,14 @@ class ChartEditorDialogHandler
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -517,14 +274,14 @@ class ChartEditorDialogHandler
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -556,14 +313,14 @@ class ChartEditorDialogHandler
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -595,14 +352,14 @@ class ChartEditorDialogHandler
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -654,14 +411,14 @@ class ChartEditorDialogHandler
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 5! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 4! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -669,14 +426,14 @@ class ChartEditorDialogHandler
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
state.openWelcomeDialog(closable);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -715,7 +472,7 @@ class ChartEditorDialogHandler
|
||||
|
||||
var instId:String = state.currentInstrumentalId;
|
||||
|
||||
var onDropFile:String->Void;
|
||||
var dropHandler:DialogDropTarget = {component: instrumentalBox, handler: null};
|
||||
|
||||
instrumentalBox.onClick = function(_) {
|
||||
Dialogs.openBinaryFile('Open Instrumental', [
|
||||
@@ -728,7 +485,7 @@ class ChartEditorDialogHandler
|
||||
|
||||
state.switchToCurrentInstrumental();
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
state.removeDropHandler(dropHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -738,7 +495,7 @@ class ChartEditorDialogHandler
|
||||
});
|
||||
}
|
||||
|
||||
onDropFile = function(pathStr:String) {
|
||||
var onDropFile:String->Void = function(pathStr:String) {
|
||||
var path:Path = new Path(pathStr);
|
||||
trace('Dropped file (${path})');
|
||||
if (state.loadInstFromPath(path, instId))
|
||||
@@ -748,7 +505,7 @@ class ChartEditorDialogHandler
|
||||
|
||||
state.switchToCurrentInstrumental();
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
state.removeDropHandler(dropHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -766,7 +523,9 @@ class ChartEditorDialogHandler
|
||||
}
|
||||
};
|
||||
|
||||
addDropHandler(instrumentalBox, onDropFile);
|
||||
dropHandler.handler = onDropFile;
|
||||
|
||||
state.addDropHandler(dropHandler);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
@@ -968,7 +727,7 @@ class ChartEditorDialogHandler
|
||||
var charMetadata:Null<CharacterData> = CharacterDataParser.fetchCharacterData(charKey);
|
||||
var charName:String = charMetadata != null ? charMetadata.name : charKey;
|
||||
|
||||
var vocalsEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
|
||||
var vocalsEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
|
||||
|
||||
var vocalsEntryLabel:Null<Label> = vocalsEntry.findComponent('vocalsEntryLabel', Label);
|
||||
if (vocalsEntryLabel == null) throw 'Could not locate vocalsEntryLabel in Upload Vocals dialog';
|
||||
@@ -978,6 +737,8 @@ class ChartEditorDialogHandler
|
||||
vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||
#end
|
||||
|
||||
var dropHandler:DialogDropTarget = {component: vocalsEntry, handler: null};
|
||||
|
||||
var onDropFile:String->Void = function(pathStr:String) {
|
||||
trace('Selected file: $pathStr');
|
||||
var path:Path = new Path(pathStr);
|
||||
@@ -999,7 +760,7 @@ class ChartEditorDialogHandler
|
||||
#end
|
||||
|
||||
dialogNoVocals.hidden = true;
|
||||
removeDropHandler(onDropFile);
|
||||
state.removeDropHandler(dropHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1015,7 +776,9 @@ class ChartEditorDialogHandler
|
||||
}
|
||||
};
|
||||
|
||||
vocalsEntry.onClick = function(_) {
|
||||
dropHandler.handler = onDropFile;
|
||||
|
||||
vocalsEntry.onClick = function(_event) {
|
||||
Dialogs.openBinaryFile('Open $charName Vocals', [
|
||||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile) {
|
||||
if (selectedFile != null && selectedFile.bytes != null)
|
||||
@@ -1057,7 +820,7 @@ class ChartEditorDialogHandler
|
||||
|
||||
// onDropFile
|
||||
#if FILE_DROP_SUPPORTED
|
||||
addDropHandler(vocalsEntry, onDropFile);
|
||||
addDropHandler(dropHandler);
|
||||
#end
|
||||
dialogContainer.addComponent(vocalsEntry);
|
||||
}
|
||||
@@ -1119,7 +882,7 @@ class ChartEditorDialogHandler
|
||||
}
|
||||
|
||||
// Build an entry for -chart.json.
|
||||
var songDefaultChartDataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var songDefaultChartDataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var songDefaultChartDataEntryLabel:Null<Label> = songDefaultChartDataEntry.findComponent('chartEntryLabel', Label);
|
||||
if (songDefaultChartDataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
||||
#if FILE_DROP_SUPPORTED
|
||||
@@ -1129,13 +892,17 @@ class ChartEditorDialogHandler
|
||||
#end
|
||||
|
||||
songDefaultChartDataEntry.onClick = onClickChartDataVariation.bind(Constants.DEFAULT_VARIATION).bind(songDefaultChartDataEntryLabel);
|
||||
addDropHandler(songDefaultChartDataEntry, onDropFileChartDataVariation.bind(Constants.DEFAULT_VARIATION).bind(songDefaultChartDataEntryLabel));
|
||||
state.addDropHandler(
|
||||
{
|
||||
component: songDefaultChartDataEntry,
|
||||
handler: onDropFileChartDataVariation.bind(Constants.DEFAULT_VARIATION).bind(songDefaultChartDataEntryLabel)
|
||||
});
|
||||
chartContainerB.addComponent(songDefaultChartDataEntry);
|
||||
|
||||
for (variation in variations)
|
||||
{
|
||||
// Build entries for -metadata-<variation>.json.
|
||||
var songVariationMetadataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var songVariationMetadataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var songVariationMetadataEntryLabel:Null<Label> = songVariationMetadataEntry.findComponent('chartEntryLabel', Label);
|
||||
if (songVariationMetadataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
||||
#if FILE_DROP_SUPPORTED
|
||||
@@ -1159,7 +926,7 @@ class ChartEditorDialogHandler
|
||||
chartContainerB.addComponent(songVariationMetadataEntry);
|
||||
|
||||
// Build entries for -chart-<variation>.json.
|
||||
var songVariationChartDataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var songVariationChartDataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var songVariationChartDataEntryLabel:Null<Label> = songVariationChartDataEntry.findComponent('chartEntryLabel', Label);
|
||||
if (songVariationChartDataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
||||
#if FILE_DROP_SUPPORTED
|
||||
@@ -1346,7 +1113,7 @@ class ChartEditorDialogHandler
|
||||
});
|
||||
}
|
||||
|
||||
var metadataEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var metadataEntry:Component = RuntimeComponentBuilder.fromAsset(CHART_EDITOR_DIALOG_OPEN_CHART_PARTS_ENTRY_LAYOUT);
|
||||
var metadataEntryLabel:Null<Label> = metadataEntry.findComponent('chartEntryLabel', Label);
|
||||
if (metadataEntryLabel == null) throw 'Could not locate chartEntryLabel in Open Chart dialog';
|
||||
|
||||
@@ -1357,8 +1124,8 @@ class ChartEditorDialogHandler
|
||||
#end
|
||||
|
||||
metadataEntry.onClick = onClickMetadataVariation.bind(Constants.DEFAULT_VARIATION).bind(metadataEntryLabel);
|
||||
addDropHandler(metadataEntry, onDropFileMetadataVariation.bind(Constants.DEFAULT_VARIATION).bind(metadataEntryLabel));
|
||||
metadataEntry.onMouseOver = function(_) {
|
||||
state.addDropHandler({component: metadataEntry, handler: onDropFileMetadataVariation.bind(Constants.DEFAULT_VARIATION).bind(metadataEntryLabel)});
|
||||
metadataEntry.onMouseOver = function(_event) {
|
||||
metadataEntry.swapClass('upload-bg', 'upload-bg-hover');
|
||||
Cursor.cursorMode = Pointer;
|
||||
}
|
||||
@@ -1459,7 +1226,7 @@ class ChartEditorDialogHandler
|
||||
state.success('Success', 'Loaded chart file (${path.file}.${path.ext})');
|
||||
};
|
||||
|
||||
addDropHandler(importBox, onDropFile);
|
||||
state.addDropHandler({component: importBox, handler: onDropFile});
|
||||
|
||||
return dialog;
|
||||
}
|
||||
@@ -1633,7 +1400,7 @@ class ChartEditorDialogHandler
|
||||
*/
|
||||
static function openDialog(state:ChartEditorState, key:String, modal:Bool = true, closable:Bool = true):Null<Dialog>
|
||||
{
|
||||
var dialog:Null<Dialog> = cast state.buildComponent(key);
|
||||
var dialog:Null<Dialog> = cast RuntimeComponentBuilder.fromAsset(key);
|
||||
if (dialog == null) return null;
|
||||
|
||||
dialog.destroyOnClose = true;
|
||||
@@ -1650,14 +1417,10 @@ class ChartEditorDialogHandler
|
||||
return dialog;
|
||||
}
|
||||
|
||||
// ==========
|
||||
// DROP HANDLERS
|
||||
// ==========
|
||||
static var dropHandlers:Array<
|
||||
{
|
||||
component:Component,
|
||||
handler:(String->Void)
|
||||
}> = [];
|
||||
// ===============
|
||||
// DROP HANDLERS
|
||||
// ===============
|
||||
static var dropHandlers:Array<DialogDropTarget> = [];
|
||||
|
||||
/**
|
||||
* Add a callback for when a file is dropped on a component.
|
||||
@@ -1665,32 +1428,33 @@ class ChartEditorDialogHandler
|
||||
* On OS X you can’t drop on the application window, but rather only the app icon
|
||||
* (either in the dock while running or the icon on the hard drive) so this must be disabled
|
||||
* and UI updated appropriately.
|
||||
* @param component
|
||||
* @param handler
|
||||
*/
|
||||
static function addDropHandler(component:Component, handler:String->Void):Void
|
||||
public static function addDropHandler(state:ChartEditorState, dropTarget:DialogDropTarget):Void
|
||||
{
|
||||
#if desktop
|
||||
if (!FlxG.stage.window.onDropFile.has(onDropFile)) FlxG.stage.window.onDropFile.add(onDropFile);
|
||||
|
||||
dropHandlers.push(
|
||||
{
|
||||
component: component,
|
||||
handler: handler
|
||||
});
|
||||
dropHandlers.push(dropTarget);
|
||||
#else
|
||||
trace('addDropHandler not implemented for this platform');
|
||||
#end
|
||||
}
|
||||
|
||||
static function removeDropHandler(handler:String->Void):Void
|
||||
/**
|
||||
* Remove a callback for when a file is dropped on a component.
|
||||
*/
|
||||
public static function removeDropHandler(state:ChartEditorState, dropTarget:DialogDropTarget):Void
|
||||
{
|
||||
#if desktop
|
||||
FlxG.stage.window.onDropFile.remove(handler);
|
||||
dropHandlers.remove(dropTarget);
|
||||
#end
|
||||
}
|
||||
|
||||
static function clearDropHandlers():Void
|
||||
/**
|
||||
* Clear ALL drop handlers, including the core handler.
|
||||
* Call this only when leaving the chart editor entirely.
|
||||
*/
|
||||
public static function clearDropHandlers(state:ChartEditorState):Void
|
||||
{
|
||||
#if desktop
|
||||
dropHandlers = [];
|
||||
@@ -1698,10 +1462,12 @@ class ChartEditorDialogHandler
|
||||
#end
|
||||
}
|
||||
|
||||
static final EPSILON:Float = 0.01;
|
||||
|
||||
static function onDropFile(path:String):Void
|
||||
{
|
||||
// a VERY short timer to wait for the mouse position to update
|
||||
new FlxTimer().start(0.01, function(_) {
|
||||
new FlxTimer().start(EPSILON, function(_) {
|
||||
for (handler in dropHandlers)
|
||||
{
|
||||
if (handler.component.hitTest(FlxG.mouse.screenX, FlxG.mouse.screenY))
|
||||
|
||||
@@ -7,28 +7,28 @@ class ChartEditorShortcutHandler
|
||||
{
|
||||
public static function applyPlatformShortcutText(state:ChartEditorState):Void
|
||||
{
|
||||
state.setComponentShortcutText('menubarItemNewChart', ctrlOrCmd('N'));
|
||||
state.setComponentShortcutText('menubarItemOpenChart', ctrlOrCmd('O'));
|
||||
state.setComponentShortcutText('menubarItemSaveChartAs', ctrlOrCmd(shift('S')));
|
||||
state.setComponentShortcutText('menubarItemExit', ctrlOrCmd('Q'));
|
||||
state.menubarItemNewChart.shortcutText = ctrlOrCmd('N');
|
||||
state.menubarItemOpenChart.shortcutText = ctrlOrCmd('O');
|
||||
state.menubarItemSaveChartAs.shortcutText = ctrlOrCmd(shift('S'));
|
||||
state.menubarItemExit.shortcutText = ctrlOrCmd('Q');
|
||||
|
||||
state.setComponentShortcutText('menubarItemUndo', ctrlOrCmd('Z'));
|
||||
state.setComponentShortcutText('menubarItemRedo', ctrlOrCmd('Y'));
|
||||
state.setComponentShortcutText('menubarItemCut', ctrlOrCmd('X'));
|
||||
state.setComponentShortcutText('menubarItemCopy', ctrlOrCmd('C'));
|
||||
state.setComponentShortcutText('menubarItemPaste', ctrlOrCmd('V'));
|
||||
state.menubarItemUndo.shortcutText = ctrlOrCmd('Z');
|
||||
state.menubarItemRedo.shortcutText = ctrlOrCmd('Y');
|
||||
state.menubarItemCut.shortcutText = ctrlOrCmd('X');
|
||||
state.menubarItemCopy.shortcutText = ctrlOrCmd('C');
|
||||
state.menubarItemPaste.shortcutText = ctrlOrCmd('V');
|
||||
|
||||
state.setComponentShortcutText('menubarItemSelectAll', ctrlOrCmd('A'));
|
||||
state.setComponentShortcutText('menubarItemSelectInverse', ctrlOrCmd('I'));
|
||||
state.setComponentShortcutText('menubarItemSelectNone', ctrlOrCmd('D'));
|
||||
state.setComponentShortcutText('menubarItemSelectBeforeCursor', shift('Home'));
|
||||
state.setComponentShortcutText('menubarItemSelectAfterCursor', shift('End'));
|
||||
state.menubarItemSelectAll.shortcutText = ctrlOrCmd('A');
|
||||
state.menubarItemSelectInverse.shortcutText = ctrlOrCmd('I');
|
||||
state.menubarItemSelectNone.shortcutText = ctrlOrCmd('D');
|
||||
state.menubarItemSelectBeforeCursor.shortcutText = shift('Home');
|
||||
state.menubarItemSelectAfterCursor.shortcutText = shift('End');
|
||||
|
||||
state.setComponentShortcutText('menubarItemDifficultyDown', ctrlOrCmd('←'));
|
||||
state.setComponentShortcutText('menubarItemDifficultyUp', ctrlOrCmd('→'));
|
||||
state.menubarItemDifficultyDown.shortcutText = ctrlOrCmd('←');
|
||||
state.menubarItemDifficultyUp.shortcutText = ctrlOrCmd('→');
|
||||
|
||||
state.setComponentShortcutText('menubarItemPlaytestFull', 'Enter');
|
||||
state.setComponentShortcutText('menubarItemPlaytestMinimal', shift('Enter'));
|
||||
state.menubarItemPlaytestFull.shortcutText = 'Enter';
|
||||
state.menubarItemPlaytestMinimal.shortcutText = shift('Enter');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ import funkin.play.event.SongEvent;
|
||||
import funkin.play.song.SongSerializer;
|
||||
import funkin.play.stage.StageData;
|
||||
import funkin.play.stage.StageData.StageDataParser;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import funkin.ui.debug.charting.util.ChartEditorDropdowns;
|
||||
import funkin.ui.haxeui.components.CharacterPlayer;
|
||||
import funkin.util.FileUtil;
|
||||
@@ -217,7 +218,7 @@ class ChartEditorToolboxHandler
|
||||
|
||||
static function buildToolboxNoteDataLayout(state:ChartEditorState):Null<CollapsibleDialog>
|
||||
{
|
||||
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT);
|
||||
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
@@ -226,7 +227,7 @@ class ChartEditorToolboxHandler
|
||||
toolbox.y = 100;
|
||||
|
||||
toolbox.onDialogClosed = function(event:DialogEvent) {
|
||||
state.setUICheckboxSelected('menubarItemToggleToolboxNotes', false);
|
||||
state.menubarItemToggleToolboxNotes.selected = false;
|
||||
}
|
||||
|
||||
var toolboxNotesNoteKind:Null<DropDown> = toolbox.findComponent('toolboxNotesNoteKind', DropDown);
|
||||
@@ -269,7 +270,7 @@ class ChartEditorToolboxHandler
|
||||
|
||||
static function buildToolboxEventDataLayout(state:ChartEditorState):Null<CollapsibleDialog>
|
||||
{
|
||||
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT);
|
||||
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENTDATA_LAYOUT);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
@@ -278,7 +279,7 @@ class ChartEditorToolboxHandler
|
||||
toolbox.y = 150;
|
||||
|
||||
toolbox.onDialogClosed = function(event:DialogEvent) {
|
||||
state.setUICheckboxSelected('menubarItemToggleToolboxEvents', false);
|
||||
state.menubarItemToggleToolboxEvents.selected = false;
|
||||
}
|
||||
|
||||
var toolboxEventsEventKind:Null<DropDown> = toolbox.findComponent('toolboxEventsEventKind', DropDown);
|
||||
@@ -418,7 +419,7 @@ class ChartEditorToolboxHandler
|
||||
|
||||
static function buildToolboxDifficultyLayout(state:ChartEditorState):Null<CollapsibleDialog>
|
||||
{
|
||||
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
@@ -427,7 +428,7 @@ class ChartEditorToolboxHandler
|
||||
toolbox.y = 200;
|
||||
|
||||
toolbox.onDialogClosed = function(event:UIEvent) {
|
||||
state.setUICheckboxSelected('menubarItemToggleToolboxDifficulty', false);
|
||||
state.menubarItemToggleToolboxDifficulty.selected = false;
|
||||
}
|
||||
|
||||
var difficultyToolboxAddVariation:Null<Button> = toolbox.findComponent('difficultyToolboxAddVariation', Button);
|
||||
@@ -505,7 +506,7 @@ class ChartEditorToolboxHandler
|
||||
|
||||
static function buildToolboxMetadataLayout(state:ChartEditorState):Null<CollapsibleDialog>
|
||||
{
|
||||
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
@@ -514,7 +515,7 @@ class ChartEditorToolboxHandler
|
||||
toolbox.y = 250;
|
||||
|
||||
toolbox.onDialogClosed = function(event:UIEvent) {
|
||||
state.setUICheckboxSelected('menubarItemToggleToolboxMetadata', false);
|
||||
state.menubarItemToggleToolboxMetadata.selected = false;
|
||||
}
|
||||
|
||||
var inputSongName:Null<TextField> = toolbox.findComponent('inputSongName', TextField);
|
||||
@@ -656,7 +657,7 @@ class ChartEditorToolboxHandler
|
||||
|
||||
static function buildToolboxPlayerPreviewLayout(state:ChartEditorState):Null<CollapsibleDialog>
|
||||
{
|
||||
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT);
|
||||
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
@@ -665,7 +666,7 @@ class ChartEditorToolboxHandler
|
||||
toolbox.y = 350;
|
||||
|
||||
toolbox.onDialogClosed = function(event:DialogEvent) {
|
||||
state.setUICheckboxSelected('menubarItemToggleToolboxPlayerPreview', false);
|
||||
state.menubarItemToggleToolboxPlayerPreview.selected = false;
|
||||
}
|
||||
|
||||
var charPlayer:Null<CharacterPlayer> = toolbox.findComponent('charPlayer');
|
||||
@@ -685,7 +686,7 @@ class ChartEditorToolboxHandler
|
||||
|
||||
static function buildToolboxOpponentPreviewLayout(state:ChartEditorState):Null<CollapsibleDialog>
|
||||
{
|
||||
var toolbox:CollapsibleDialog = cast state.buildComponent(ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT);
|
||||
var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT);
|
||||
|
||||
if (toolbox == null) return null;
|
||||
|
||||
@@ -694,7 +695,7 @@ class ChartEditorToolboxHandler
|
||||
toolbox.y = 350;
|
||||
|
||||
toolbox.onDialogClosed = (event:DialogEvent) -> {
|
||||
state.setUICheckboxSelected('menubarItemToggleToolboxOpponentPreview', false);
|
||||
state.menubarItemToggleToolboxOpponentPreview.selected = false;
|
||||
}
|
||||
|
||||
var charPlayer:Null<CharacterPlayer> = toolbox.findComponent('charPlayer');
|
||||
|
||||
@@ -267,7 +267,7 @@ class LatencyState extends MusicBeatSubState
|
||||
|
||||
function generateBeatStuff()
|
||||
{
|
||||
Conductor.songPosition = swagSong.getTimeWithDiff();
|
||||
Conductor.update(swagSong.getTimeWithDiff());
|
||||
|
||||
var closestBeat:Int = Math.round(Conductor.songPosition / Conductor.beatLengthMs) % diffGrp.members.length;
|
||||
var getDiff:Float = Conductor.songPosition - (closestBeat * Conductor.beatLengthMs);
|
||||
|
||||
Reference in New Issue
Block a user