mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 10:05:41 -05:00
Merge pull request #20 from FunkinCrew/chart-editor-fixes-with-dave
Chart editor fixes with dave
This commit is contained in:
commit
bf7a42d143
17 changed files with 742 additions and 490 deletions
|
@ -156,9 +156,13 @@
|
|||
<haxeflag name="--macro" value="include('funkin')" />
|
||||
|
||||
<!-- Ensure all UI components are available at runtime. -->
|
||||
<haxeflag name="--macro" value="include('haxe.ui.backend.flixel.components')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.dialogs')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.menus')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.properties')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.core')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.components')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers')" />
|
||||
<haxeflag name="--macro" value="include('haxe.ui.containers.menus')" />
|
||||
|
||||
<!--
|
||||
Ensure additional class packages are available at runtime (some only really used by scripts).
|
||||
|
|
|
@ -193,7 +193,8 @@
|
|||
{
|
||||
"props": {
|
||||
"max": 1000,
|
||||
"ignoreEmptyLines": true
|
||||
"ignoreEmptyLines": true,
|
||||
"severity": "IGNORE"
|
||||
},
|
||||
"type": "FileLength"
|
||||
},
|
||||
|
@ -232,7 +233,7 @@
|
|||
},
|
||||
{
|
||||
"props": {
|
||||
"ignoreReturnAssignments": false,
|
||||
"ignoreReturnAssignments": true,
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "InnerAssignment"
|
||||
|
@ -392,12 +393,13 @@
|
|||
},
|
||||
{
|
||||
"props": {
|
||||
"oldFunctionTypePolicy": "none",
|
||||
"unaryOpPolicy": "none",
|
||||
"intervalOpPolicy": "none",
|
||||
|
||||
"newFunctionTypePolicy": "around",
|
||||
"ternaryOpPolicy": "around",
|
||||
"unaryOpPolicy": "none",
|
||||
"oldFunctionTypePolicy": "around",
|
||||
"boolOpPolicy": "around",
|
||||
"intervalOpPolicy": "none",
|
||||
"assignOpPolicy": "around",
|
||||
"bitwiseOpPolicy": "around",
|
||||
"arithmeticOpPolicy": "around",
|
||||
|
@ -623,7 +625,9 @@
|
|||
"type": "UnusedImport"
|
||||
},
|
||||
{
|
||||
"props": {},
|
||||
"props": {
|
||||
"severity": "WARNING"
|
||||
},
|
||||
"type": "UnusedLocalVar"
|
||||
},
|
||||
{
|
||||
|
|
3
haxe_libraries/README.md
Normal file
3
haxe_libraries/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# haxe_libraries
|
||||
|
||||
Used by Lix
|
6
hmm.json
6
hmm.json
|
@ -42,14 +42,14 @@
|
|||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "e5cf78d",
|
||||
"ref": "59157d2",
|
||||
"url": "https://github.com/haxeui/haxeui-core/"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-flixel",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "f03bb6d",
|
||||
"ref": "d353389",
|
||||
"url": "https://github.com/haxeui/haxeui-flixel"
|
||||
},
|
||||
{
|
||||
|
@ -68,7 +68,7 @@
|
|||
"name": "hxcodec",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "2c9a89a",
|
||||
"ref": "d74c2aa",
|
||||
"url": "https://github.com/polybiusproxy/hxCodec"
|
||||
},
|
||||
{
|
||||
|
|
116
source/funkin/input/TurboKeyHandler.hx
Normal file
116
source/funkin/input/TurboKeyHandler.hx
Normal file
|
@ -0,0 +1,116 @@
|
|||
package funkin.input;
|
||||
|
||||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.FlxBasic;
|
||||
|
||||
/**
|
||||
* Handles repeating behavior when holding down a key or key combination.
|
||||
*
|
||||
* When the `keys` are pressed, `activated` will be true for the first frame,
|
||||
* then wait `delay` seconds before becoming true for one frame every `interval` seconds.
|
||||
*
|
||||
* Example: Pressing Ctrl+Z will undo, while holding Ctrl+Z will start to undo repeatedly.
|
||||
*/
|
||||
class TurboKeyHandler extends FlxBasic
|
||||
{
|
||||
/**
|
||||
* Default delay before repeating.
|
||||
*/
|
||||
static inline final DEFAULT_DELAY:Float = 0.4;
|
||||
|
||||
/**
|
||||
* Default interval between repeats.
|
||||
*/
|
||||
static inline final DEFAULT_INTERVAL:Float = 0.1;
|
||||
|
||||
/**
|
||||
* Whether all of the keys for this handler are pressed.
|
||||
*/
|
||||
public var allPressed(get, null):Bool;
|
||||
|
||||
/**
|
||||
* Whether all of the keys for this handler are activated,
|
||||
* and the handler is ready to repeat.
|
||||
*/
|
||||
public var activated(default, null):Bool = false;
|
||||
|
||||
var keys:Array<FlxKey>;
|
||||
var delay:Float;
|
||||
var interval:Float;
|
||||
|
||||
var allPressedTime:Float = 0;
|
||||
|
||||
function new(keys:Array<FlxKey>, delay:Float = DEFAULT_DELAY, interval:Float = DEFAULT_INTERVAL)
|
||||
{
|
||||
super();
|
||||
this.keys = keys;
|
||||
this.delay = delay;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
function get_allPressed():Bool
|
||||
{
|
||||
if (keys == null || keys.length == 0) return false;
|
||||
if (keys.length == 1) return FlxG.keys.anyPressed(keys);
|
||||
|
||||
// Check if ANY keys are unpressed
|
||||
for (key in keys)
|
||||
{
|
||||
if (!FlxG.keys.anyPressed([key])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float):Void
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (allPressed)
|
||||
{
|
||||
if (allPressedTime == 0)
|
||||
{
|
||||
activated = true;
|
||||
}
|
||||
else if (allPressedTime >= (delay + interval))
|
||||
{
|
||||
activated = true;
|
||||
allPressedTime -= interval;
|
||||
}
|
||||
else
|
||||
{
|
||||
activated = false;
|
||||
}
|
||||
allPressedTime += elapsed;
|
||||
}
|
||||
else
|
||||
{
|
||||
allPressedTime = 0;
|
||||
activated = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a TurboKeyHandler that monitors from a single key.
|
||||
* @param inputKey The key to monitor.
|
||||
* @param delay How long to wait before repeating.
|
||||
* @param repeatDelay How long to wait between repeats.
|
||||
* @return A TurboKeyHandler
|
||||
*/
|
||||
public static overload inline extern function build(inputKey:FlxKey, ?delay:Float = DEFAULT_DELAY, ?interval:Float = DEFAULT_INTERVAL):TurboKeyHandler
|
||||
{
|
||||
return new TurboKeyHandler([inputKey], delay, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a TurboKeyHandler that monitors a key combination.
|
||||
* @param inputKeys The combination of keys to monitor.
|
||||
* @param delay How long to wait before repeating.
|
||||
* @param repeatDelay How long to wait between repeats.
|
||||
* @return A TurboKeyHandler
|
||||
*/
|
||||
public static overload inline extern function build(inputKeys:Array<FlxKey>, ?delay:Float = DEFAULT_DELAY,
|
||||
?interval:Float = DEFAULT_INTERVAL):TurboKeyHandler
|
||||
{
|
||||
return new TurboKeyHandler(inputKeys, delay, interval);
|
||||
}
|
||||
}
|
|
@ -169,6 +169,7 @@ class PolymodHandler
|
|||
// `polymod.*`
|
||||
for (cls in ClassMacro.listClassesInPackage('polymod'))
|
||||
{
|
||||
if (cls == null) continue;
|
||||
var className = Type.getClassName(cls);
|
||||
Polymod.blacklistImport(className);
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
|
|||
|
||||
function set_active(value:Bool):Bool
|
||||
{
|
||||
this.active = value;
|
||||
return value;
|
||||
return this.active = value;
|
||||
}
|
||||
|
||||
public var moduleId(default, null):String = 'UNKNOWN';
|
||||
|
|
|
@ -436,22 +436,19 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
if (!exists || x == value) return x; // early return (no need to transform)
|
||||
|
||||
transformChildren(xTransform, value - x); // offset
|
||||
x = value;
|
||||
return x;
|
||||
return x = value;
|
||||
}
|
||||
|
||||
override function set_y(value:Float):Float
|
||||
{
|
||||
if (exists && y != value) transformChildren(yTransform, value - y); // offset
|
||||
y = value;
|
||||
return y;
|
||||
return y = value;
|
||||
}
|
||||
|
||||
override function set_angle(value:Float):Float
|
||||
{
|
||||
if (exists && angle != value) transformChildren(angleTransform, value - angle); // offset
|
||||
angle = value;
|
||||
return angle;
|
||||
return angle = value;
|
||||
}
|
||||
|
||||
override function set_alpha(value:Float):Float
|
||||
|
@ -462,43 +459,37 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
{
|
||||
transformChildren(directAlphaTransform, value);
|
||||
}
|
||||
alpha = value;
|
||||
return alpha;
|
||||
return alpha = value;
|
||||
}
|
||||
|
||||
override function set_facing(value:Int):Int
|
||||
{
|
||||
if (exists && facing != value) transformChildren(facingTransform, value);
|
||||
facing = value;
|
||||
return facing;
|
||||
return facing = value;
|
||||
}
|
||||
|
||||
override function set_flipX(value:Bool):Bool
|
||||
{
|
||||
if (exists && flipX != value) transformChildren(flipXTransform, value);
|
||||
flipX = value;
|
||||
return flipX;
|
||||
return flipX = value;
|
||||
}
|
||||
|
||||
override function set_flipY(value:Bool):Bool
|
||||
{
|
||||
if (exists && flipY != value) transformChildren(flipYTransform, value);
|
||||
flipY = value;
|
||||
return flipY;
|
||||
return flipY = value;
|
||||
}
|
||||
|
||||
override function set_moves(value:Bool):Bool
|
||||
{
|
||||
if (exists && moves != value) transformChildren(movesTransform, value);
|
||||
moves = value;
|
||||
return moves;
|
||||
return moves = value;
|
||||
}
|
||||
|
||||
override function set_immovable(value:Bool):Bool
|
||||
{
|
||||
if (exists && immovable != value) transformChildren(immovableTransform, value);
|
||||
immovable = value;
|
||||
return immovable;
|
||||
return immovable = value;
|
||||
}
|
||||
|
||||
override function set_solid(value:Bool):Bool
|
||||
|
@ -510,15 +501,13 @@ class AnimateAtlasCharacter extends BaseCharacter
|
|||
override function set_color(value:Int):Int
|
||||
{
|
||||
if (exists && color != value) transformChildren(gColorTransform, value);
|
||||
color = value;
|
||||
return color;
|
||||
return color = value;
|
||||
}
|
||||
|
||||
override function set_blend(value:BlendMode):BlendMode
|
||||
{
|
||||
if (exists && blend != value) transformChildren(blendTransform, value);
|
||||
blend = value;
|
||||
return blend;
|
||||
return blend = value;
|
||||
}
|
||||
|
||||
override function set_clipRect(rect:FlxRect):FlxRect
|
||||
|
|
|
@ -60,7 +60,7 @@ class VanillaCutscenes
|
|||
#if html5
|
||||
// Video displays OVER the FlxState.
|
||||
vid = new FlxVideo(path);
|
||||
vid.finishCallback = finishCutscene;
|
||||
vid.finishCallback = finishCutscene.bind(0.5);
|
||||
#else
|
||||
// Video displays OVER the FlxState.
|
||||
// vid = new FlxVideoSprite(0, 0);
|
||||
|
|
|
@ -126,8 +126,10 @@ class Song // implements IPlayStateScriptedClass
|
|||
|
||||
/**
|
||||
* Retrieve the metadata for a specific difficulty, including the chart if it is loaded.
|
||||
* @param diffId The difficulty ID, such as `easy` or `hard`.
|
||||
* @return The difficulty data.
|
||||
*/
|
||||
public inline function getDifficulty(?diffId:String):SongDifficulty
|
||||
public inline function getDifficulty(diffId:String = null):SongDifficulty
|
||||
{
|
||||
if (diffId == null) diffId = difficulties.keys().array()[0];
|
||||
|
||||
|
|
|
@ -70,8 +70,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
|
|||
|
||||
this.x += xDiff;
|
||||
this.y += yDiff;
|
||||
globalOffsets = value;
|
||||
return value;
|
||||
return globalOffsets = value;
|
||||
}
|
||||
|
||||
var animOffsets(default, set):Array<Float> = [0, 0];
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxTimer;
|
||||
import funkin.input.Cursor;
|
||||
import funkin.play.character.BaseCharacter;
|
||||
import funkin.play.character.CharacterData.CharacterDataParser;
|
||||
import funkin.play.song.Song;
|
||||
import funkin.play.song.SongData.SongDataParser;
|
||||
import funkin.play.song.SongData.SongPlayableChar;
|
||||
import funkin.play.song.SongData.SongTimeChange;
|
||||
import haxe.io.Path;
|
||||
import haxe.ui.components.Button;
|
||||
import haxe.ui.components.DropDown;
|
||||
import haxe.ui.components.Image;
|
||||
import haxe.ui.components.Label;
|
||||
import haxe.ui.components.Link;
|
||||
import haxe.ui.components.NumberStepper;
|
||||
|
@ -18,28 +18,34 @@ import haxe.ui.components.TextField;
|
|||
import haxe.ui.containers.Box;
|
||||
import haxe.ui.containers.dialogs.Dialog;
|
||||
import haxe.ui.containers.dialogs.Dialogs;
|
||||
import haxe.ui.containers.properties.Property;
|
||||
import haxe.ui.containers.properties.PropertyGrid;
|
||||
import haxe.ui.containers.properties.PropertyGroup;
|
||||
import haxe.ui.containers.VBox;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.notifications.NotificationManager;
|
||||
import haxe.ui.notifications.NotificationType;
|
||||
|
||||
using Lambda;
|
||||
|
||||
/**
|
||||
* Handles dialogs for the new Chart Editor.
|
||||
*/
|
||||
class ChartEditorDialogHandler
|
||||
{
|
||||
static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT = Paths.ui('chart-editor/dialogs/about');
|
||||
static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT = Paths.ui('chart-editor/dialogs/welcome');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT = Paths.ui('chart-editor/dialogs/upload-inst');
|
||||
static final CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT = Paths.ui('chart-editor/dialogs/song-metadata');
|
||||
static final CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT = Paths.ui('chart-editor/dialogs/song-metadata-chargroup');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT = Paths.ui('chart-editor/dialogs/upload-vocals');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT = Paths.ui('chart-editor/dialogs/upload-vocals-entry');
|
||||
static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT = Paths.ui('chart-editor/dialogs/user-guide');
|
||||
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_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');
|
||||
static final CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT:String = Paths.ui('chart-editor/dialogs/song-metadata-chargroup');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals');
|
||||
static final CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT:String = Paths.ui('chart-editor/dialogs/upload-vocals-entry');
|
||||
static final CHART_EDITOR_DIALOG_USER_GUIDE_LAYOUT:String = Paths.ui('chart-editor/dialogs/user-guide');
|
||||
|
||||
/**
|
||||
*
|
||||
* Builds and opens a dialog giving brief credits for the chart editor.
|
||||
* @param state The current chart editor state.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static inline function openAboutDialog(state:ChartEditorState):Dialog
|
||||
{
|
||||
|
@ -48,105 +54,70 @@ class ChartEditorDialogHandler
|
|||
|
||||
/**
|
||||
* Builds and opens a dialog letting the user create a new chart, open a recent chart, or load from a template.
|
||||
* @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 openWelcomeDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
||||
{
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable);
|
||||
|
||||
// TODO: Add callbacks to the dialog buttons
|
||||
|
||||
// Switch the graphic for frames.
|
||||
var bfSpritePlaceholder:Image = dialog.findComponent('bfSprite', Image);
|
||||
|
||||
// TODO: Replace this bullshit with a custom HaxeUI component that loads the sprite from the game's assets.
|
||||
|
||||
if (bfSpritePlaceholder != null)
|
||||
{
|
||||
var bfSprite:FlxSprite = new FlxSprite(0, 0);
|
||||
|
||||
bfSprite.visible = false;
|
||||
|
||||
var frames = Paths.getSparrowAtlas(bfSpritePlaceholder.resource);
|
||||
bfSprite.frames = frames;
|
||||
|
||||
bfSprite.animation.addByPrefix('idle', 'Boyfriend DJ0', 24, true);
|
||||
bfSprite.animation.play('idle');
|
||||
|
||||
bfSpritePlaceholder.rootComponent.add(bfSprite);
|
||||
bfSpritePlaceholder.visible = false;
|
||||
|
||||
new FlxTimer().start(0.10, (_timer:FlxTimer) ->
|
||||
{
|
||||
bfSprite.x = bfSpritePlaceholder.screenLeft;
|
||||
bfSprite.y = bfSpritePlaceholder.screenTop;
|
||||
bfSprite.setGraphicSize(Std.int(bfSpritePlaceholder.width), Std.int(bfSpritePlaceholder.height));
|
||||
bfSprite.visible = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Add handlers to the "Create From Song" section.
|
||||
var linkCreateBasic:Link = dialog.findComponent('splashCreateFromSongBasic', Link);
|
||||
linkCreateBasic.onClick = (_event) ->
|
||||
{
|
||||
linkCreateBasic.onClick = function(_event) {
|
||||
// Hide the welcome dialog
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Create song wizard
|
||||
var uploadInstDialog = openUploadInstDialog(state, false);
|
||||
uploadInstDialog.onDialogClosed = (_event) ->
|
||||
{
|
||||
//
|
||||
// Create Song Wizard
|
||||
//
|
||||
|
||||
// Step 1. Upload Instrumental
|
||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, false);
|
||||
uploadInstDialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
var songMetadataDialog = openSongMetadataDialog(state);
|
||||
songMetadataDialog.onDialogClosed = (_event) ->
|
||||
{
|
||||
// Step 2. Song Metadata
|
||||
var songMetadataDialog:Dialog = openSongMetadataDialog(state);
|
||||
songMetadataDialog.onDialogClosed = function(_event) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
if (_event.button == DialogButton.APPLY)
|
||||
{
|
||||
var uploadVocalsDialog = openUploadVocalsDialog(state);
|
||||
// Step 3. Upload Vocals
|
||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||
openUploadVocalsDialog(state, false); // var uploadVocalsDialog:Dialog
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled the wizard! Back to the welcome dialog.
|
||||
openWelcomeDialog(state);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Get the list of songs and insert them as links into the "Create From Song" section.
|
||||
|
||||
/*
|
||||
var linkTemplateDadBattle:Link = dialog.findComponent('splashTemplateDadBattle', Link);
|
||||
linkTemplateDadBattle.onClick = (_event) ->
|
||||
{
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Load song from template
|
||||
state.loadSongAsTemplate('dadbattle');
|
||||
}
|
||||
var linkTemplateBopeebo:Link = dialog.findComponent('splashTemplateBopeebo', Link);
|
||||
linkTemplateBopeebo.onClick = (_event) ->
|
||||
{
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Load song from template
|
||||
state.loadSongAsTemplate('bopeebo');
|
||||
}
|
||||
*/
|
||||
|
||||
var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox);
|
||||
|
||||
var songList:Array<String> = SongDataParser.listSongIds();
|
||||
|
||||
for (targetSongId in songList)
|
||||
{
|
||||
var songData = SongDataParser.fetchSong(targetSongId);
|
||||
var songData:Song = SongDataParser.fetchSong(targetSongId);
|
||||
|
||||
if (songData == null) continue;
|
||||
|
||||
var songName = songData.getDifficulty().songName;
|
||||
var songName:String = songData.getDifficulty().songName;
|
||||
|
||||
var linkTemplateSong:Link = new Link();
|
||||
linkTemplateSong.text = songName;
|
||||
linkTemplateSong.onClick = (_event) ->
|
||||
{
|
||||
linkTemplateSong.onClick = function(_event) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
|
||||
// Load song from template
|
||||
|
@ -159,78 +130,175 @@ class ChartEditorDialogHandler
|
|||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog where the user uploads an instrumental for the current song.
|
||||
* @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 openUploadInstDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
|
||||
{
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT, true, closable);
|
||||
|
||||
var buttonCancel:Button = dialog.findComponent('dialogCancel', Button);
|
||||
|
||||
buttonCancel.onClick = function(_event) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
var instrumentalBox:Box = dialog.findComponent('instrumentalBox', Box);
|
||||
|
||||
instrumentalBox.onMouseOver = (_event) ->
|
||||
{
|
||||
instrumentalBox.onMouseOver = function(_event) {
|
||||
instrumentalBox.swapClass('upload-bg', 'upload-bg-hover');
|
||||
Cursor.cursorMode = Pointer;
|
||||
}
|
||||
|
||||
instrumentalBox.onMouseOut = (_event) ->
|
||||
{
|
||||
instrumentalBox.onMouseOut = function(_event) {
|
||||
instrumentalBox.swapClass('upload-bg-hover', 'upload-bg');
|
||||
Cursor.cursorMode = Default;
|
||||
}
|
||||
|
||||
var onDropFile:String->Void;
|
||||
|
||||
instrumentalBox.onClick = (_event) ->
|
||||
{
|
||||
Dialogs.openBinaryFile("Open Instrumental", [
|
||||
{label: "Audio File (.ogg)", extension: "ogg"}], function(selectedFile)
|
||||
{
|
||||
if (selectedFile != null)
|
||||
{
|
||||
trace('Selected file: ' + selectedFile);
|
||||
state.loadInstrumentalFromBytes(selectedFile.bytes);
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
instrumentalBox.onClick = function(_event) {
|
||||
Dialogs.openBinaryFile('Open Instrumental', [
|
||||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
|
||||
if (selectedFile != null)
|
||||
{
|
||||
if (state.loadInstrumentalFromBytes(selectedFile.bytes))
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.fullPath);
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded instrumental track (${selectedFile.name})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Failed to load instrumental (${selectedFile.fullPath})');
|
||||
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load instrumental track (${selectedFile.name})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onDropFile = (path:String) ->
|
||||
{
|
||||
trace('Dropped file: ' + path);
|
||||
state.loadInstrumentalFromPath(path);
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
onDropFile = function(pathStr:String) {
|
||||
var path:Path = new Path(pathStr);
|
||||
trace('Dropped file (${path})');
|
||||
if (state.loadInstrumentalFromPath(path))
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded instrumental track (${path.file}.${path.ext})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tell the user the load was successful.
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load instrumental track (${path.file}.${path.ext})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
addDropHandler(onDropFile);
|
||||
addDropHandler(instrumentalBox, onDropFile);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
static function addDropHandler(handler:String->Void)
|
||||
static var dropHandlers:Array<
|
||||
{
|
||||
component:Component,
|
||||
handler:(String->Void)
|
||||
}> = [];
|
||||
|
||||
static function addDropHandler(component:Component, handler:String->Void):Void
|
||||
{
|
||||
#if desktop
|
||||
FlxG.stage.window.onDropFile.add(handler);
|
||||
if (!FlxG.stage.window.onDropFile.has(onDropFile)) FlxG.stage.window.onDropFile.add(onDropFile);
|
||||
|
||||
dropHandlers.push(
|
||||
{
|
||||
component: component,
|
||||
handler: handler
|
||||
});
|
||||
#else
|
||||
trace('addDropHandler not implemented for this platform');
|
||||
#end
|
||||
}
|
||||
|
||||
static function removeDropHandler(handler:String->Void)
|
||||
static function removeDropHandler(handler:String->Void):Void
|
||||
{
|
||||
#if desktop
|
||||
FlxG.stage.window.onDropFile.remove(handler);
|
||||
#end
|
||||
}
|
||||
|
||||
static function clearDropHandlers():Void
|
||||
{
|
||||
#if desktop
|
||||
dropHandlers = [];
|
||||
FlxG.stage.window.onDropFile.remove(onDropFile);
|
||||
#end
|
||||
}
|
||||
|
||||
static function onDropFile(path:String):Void
|
||||
{
|
||||
// a VERY short timer to wait for the mouse position to update
|
||||
new FlxTimer().start(0.01, function(_) {
|
||||
for (handler in dropHandlers)
|
||||
{
|
||||
if (handler.component.hitTest(FlxG.mouse.screenX, FlxG.mouse.screenY))
|
||||
{
|
||||
handler.handler(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the dialog in the wizard where the user can set song metadata like name and artist and BPM.
|
||||
* @param state The ChartEditorState instance.
|
||||
* @return The dialog to open.
|
||||
*/
|
||||
public static function openSongMetadataDialog(state:ChartEditorState):Dialog
|
||||
{
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
|
||||
|
||||
var buttonCancel:Button = dialog.findComponent('dialogCancel', Button);
|
||||
|
||||
buttonCancel.onClick = function(_event) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
var dialogSongName:TextField = dialog.findComponent('dialogSongName', TextField);
|
||||
dialogSongName.onChange = (event:UIEvent) ->
|
||||
{
|
||||
var valid = event.target.text != null && event.target.text != "";
|
||||
dialogSongName.onChange = function(event:UIEvent) {
|
||||
var valid:Bool = event.target.text != null && event.target.text != '';
|
||||
|
||||
if (valid)
|
||||
{
|
||||
|
@ -245,9 +313,8 @@ class ChartEditorDialogHandler
|
|||
state.currentSongMetadata.songName = null;
|
||||
|
||||
var dialogSongArtist:TextField = dialog.findComponent('dialogSongArtist', TextField);
|
||||
dialogSongArtist.onChange = (event:UIEvent) ->
|
||||
{
|
||||
var valid = event.target.text != null && event.target.text != "";
|
||||
dialogSongArtist.onChange = function(event:UIEvent) {
|
||||
var valid:Bool = event.target.text != null && event.target.text != '';
|
||||
|
||||
if (valid)
|
||||
{
|
||||
|
@ -262,29 +329,24 @@ class ChartEditorDialogHandler
|
|||
state.currentSongMetadata.artist = null;
|
||||
|
||||
var dialogStage:DropDown = dialog.findComponent('dialogStage', DropDown);
|
||||
dialogStage.onChange = (event:UIEvent) ->
|
||||
{
|
||||
var valid = event.data != null && event.data.id != null;
|
||||
|
||||
if (event.data.id == null) return;
|
||||
dialogStage.onChange = function(event:UIEvent) {
|
||||
if (event.data == null && event.data.id == null) return;
|
||||
state.currentSongMetadata.playData.stage = event.data.id;
|
||||
};
|
||||
state.currentSongMetadata.playData.stage = null;
|
||||
|
||||
var dialogNoteSkin:DropDown = dialog.findComponent('dialogNoteSkin', DropDown);
|
||||
dialogNoteSkin.onChange = (event:UIEvent) ->
|
||||
{
|
||||
dialogNoteSkin.onChange = function(event:UIEvent) {
|
||||
if (event.data.id == null) return;
|
||||
state.currentSongMetadata.playData.noteSkin = event.data.id;
|
||||
};
|
||||
state.currentSongMetadata.playData.noteSkin = null;
|
||||
|
||||
var dialogBPM:NumberStepper = dialog.findComponent('dialogBPM', NumberStepper);
|
||||
dialogBPM.onChange = (event:UIEvent) ->
|
||||
{
|
||||
dialogBPM.onChange = function(event:UIEvent) {
|
||||
if (event.value == null || event.value <= 0) return;
|
||||
|
||||
var timeChanges = state.currentSongMetadata.timeChanges;
|
||||
var timeChanges:Array<SongTimeChange> = state.currentSongMetadata.timeChanges;
|
||||
if (timeChanges == null || timeChanges.length == 0)
|
||||
{
|
||||
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])];
|
||||
|
@ -301,13 +363,9 @@ class ChartEditorDialogHandler
|
|||
|
||||
var dialogCharGrid:PropertyGrid = dialog.findComponent('dialogCharGrid', PropertyGrid);
|
||||
var dialogCharAdd:Button = dialog.findComponent('dialogCharAdd', Button);
|
||||
dialogCharAdd.onClick = (_event) ->
|
||||
{
|
||||
dialogCharAdd.onClick = function(event:UIEvent) {
|
||||
var charGroup:PropertyGroup;
|
||||
charGroup = buildCharGroup(state, null, () ->
|
||||
{
|
||||
dialogCharGrid.removeComponent(charGroup);
|
||||
});
|
||||
charGroup = buildCharGroup(state, null, () -> dialogCharGrid.removeComponent(charGroup));
|
||||
dialogCharGrid.addComponent(charGroup);
|
||||
};
|
||||
|
||||
|
@ -317,20 +375,16 @@ class ChartEditorDialogHandler
|
|||
dialogCharGrid.addComponent(buildCharGroup(state, 'bf', null));
|
||||
|
||||
var dialogContinue:Button = dialog.findComponent('dialogContinue', Button);
|
||||
dialogContinue.onClick = (_event) ->
|
||||
{
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
dialogContinue.onClick = (_event) -> dialog.hideDialog(DialogButton.APPLY);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
static function buildCharGroup(state:ChartEditorState, ?key:String = null, removeFunc:Void->Void):PropertyGroup
|
||||
static function buildCharGroup(state:ChartEditorState, key:String = null, removeFunc:Void->Void):PropertyGroup
|
||||
{
|
||||
var groupKey = key;
|
||||
var groupKey:String = key;
|
||||
|
||||
var getCharData = () ->
|
||||
{
|
||||
var getCharData:Void->SongPlayableChar = function() {
|
||||
if (groupKey == null) groupKey = 'newChar${state.currentSongMetadata.playData.playableChars.keys().count()}';
|
||||
|
||||
var result = state.currentSongMetadata.playData.playableChars.get(groupKey);
|
||||
|
@ -342,27 +396,24 @@ class ChartEditorDialogHandler
|
|||
return result;
|
||||
}
|
||||
|
||||
var moveCharGroup = (target:String) ->
|
||||
{
|
||||
var moveCharGroup:String->Void = function(target:String) {
|
||||
var charData = getCharData();
|
||||
state.currentSongMetadata.playData.playableChars.remove(groupKey);
|
||||
state.currentSongMetadata.playData.playableChars.set(target, charData);
|
||||
groupKey = target;
|
||||
}
|
||||
|
||||
var removeGroup = () ->
|
||||
{
|
||||
var removeGroup:Void->Void = function() {
|
||||
state.currentSongMetadata.playData.playableChars.remove(groupKey);
|
||||
removeFunc();
|
||||
}
|
||||
|
||||
var charData = getCharData();
|
||||
var charData:SongPlayableChar = getCharData();
|
||||
|
||||
var charGroup:PropertyGroup = cast state.buildComponent(CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT);
|
||||
|
||||
var charGroupPlayer:DropDown = charGroup.findComponent('charGroupPlayer', DropDown);
|
||||
charGroupPlayer.onChange = (event:UIEvent) ->
|
||||
{
|
||||
charGroupPlayer.onChange = function(event:UIEvent) {
|
||||
charGroup.text = event.data.text;
|
||||
moveCharGroup(event.data.id);
|
||||
};
|
||||
|
@ -374,22 +425,19 @@ class ChartEditorDialogHandler
|
|||
}
|
||||
|
||||
var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown);
|
||||
charGroupOpponent.onChange = (event:UIEvent) ->
|
||||
{
|
||||
charGroupOpponent.onChange = function(event:UIEvent) {
|
||||
charData.opponent = event.data.id;
|
||||
};
|
||||
charGroupOpponent.value = getCharData().opponent;
|
||||
|
||||
var charGroupGirlfriend:DropDown = charGroup.findComponent('charGroupGirlfriend', DropDown);
|
||||
charGroupGirlfriend.onChange = (event:UIEvent) ->
|
||||
{
|
||||
charGroupGirlfriend.onChange = function(event:UIEvent) {
|
||||
charData.girlfriend = event.data.id;
|
||||
};
|
||||
charGroupGirlfriend.value = getCharData().girlfriend;
|
||||
|
||||
var charGroupRemove:Button = charGroup.findComponent('charGroupRemove', Button);
|
||||
charGroupRemove.onClick = (_event:MouseEvent) ->
|
||||
{
|
||||
charGroupRemove.onClick = function(event:UIEvent) {
|
||||
removeGroup();
|
||||
};
|
||||
|
||||
|
@ -398,22 +446,37 @@ class ChartEditorDialogHandler
|
|||
return charGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog where the user uploads vocals for the current song.
|
||||
* @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 openUploadVocalsDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
|
||||
{
|
||||
var charIdsForVocals = [];
|
||||
var charIdsForVocals:Array<String> = [];
|
||||
|
||||
for (charKey in state.currentSongMetadata.playData.playableChars.keys())
|
||||
{
|
||||
var charData = state.currentSongMetadata.playData.playableChars.get(charKey);
|
||||
var charData:SongPlayableChar = state.currentSongMetadata.playData.playableChars.get(charKey);
|
||||
charIdsForVocals.push(charKey);
|
||||
if (charData.opponent != null) charIdsForVocals.push(charData.opponent);
|
||||
}
|
||||
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT, true, closable);
|
||||
|
||||
var dialogContainer = dialog.findComponent('vocalContainer');
|
||||
var dialogContainer:Component = dialog.findComponent('vocalContainer');
|
||||
|
||||
var onDropFile:String->Void;
|
||||
var buttonCancel:Button = dialog.findComponent('dialogCancel', Button);
|
||||
buttonCancel.onClick = function(_event) {
|
||||
dialog.hideDialog(DialogButton.CANCEL);
|
||||
}
|
||||
|
||||
var dialogNoVocals:Button = dialog.findComponent('dialogNoVocals', Button);
|
||||
dialogNoVocals.onClick = function(_event) {
|
||||
// Dismiss
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
|
||||
for (charKey in charIdsForVocals)
|
||||
{
|
||||
|
@ -421,49 +484,77 @@ class ChartEditorDialogHandler
|
|||
var charMetadata:BaseCharacter = CharacterDataParser.fetchCharacter(charKey);
|
||||
var charName:String = charMetadata.characterName;
|
||||
|
||||
var vocalsEntry = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
|
||||
var vocalsEntry:Component = state.buildComponent(CHART_EDITOR_DIALOG_UPLOAD_VOCALS_ENTRY_LAYOUT);
|
||||
|
||||
var vocalsEntryLabel:Label = vocalsEntry.findComponent('vocalsEntryLabel', Label);
|
||||
vocalsEntryLabel.text = 'Click to browse for a vocal track for $charName.';
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
|
||||
vocalsEntry.onClick = (_event) ->
|
||||
{
|
||||
Dialogs.openBinaryFile('Open $charName Vocals', [
|
||||
{label: "Audio File (.ogg)", extension: "ogg"}], function(selectedFile)
|
||||
var onDropFile:String->Void = function(pathStr:String) {
|
||||
trace('Selected file: $pathStr');
|
||||
var path:Path = new Path(pathStr);
|
||||
|
||||
if (state.loadVocalsFromPath(path, charKey))
|
||||
{
|
||||
if (selectedFile != null)
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.name + "~" + selectedFile.fullPath);
|
||||
vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}';
|
||||
state.loadVocalsFromBytes(selectedFile.bytes);
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
// Tell the user the load was successful.
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Success',
|
||||
body: 'Loaded vocal track for $charName (${path.file}.${path.ext})',
|
||||
type: NotificationType.Success,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
vocalsEntryLabel.text = 'Vocals for $charName (drag and drop, or click to browse)\nSelected file: ${path.file}.${path.ext}';
|
||||
dialogNoVocals.hidden = true;
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vocals failed to load.
|
||||
NotificationManager.instance.addNotification(
|
||||
{
|
||||
title: 'Failure',
|
||||
body: 'Failed to load vocal track (${path.file}.${path.ext})',
|
||||
type: NotificationType.Error,
|
||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||
});
|
||||
|
||||
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||
}
|
||||
};
|
||||
|
||||
vocalsEntry.onClick = function(_event) {
|
||||
Dialogs.openBinaryFile('Open $charName Vocals', [
|
||||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile) {
|
||||
if (selectedFile != null)
|
||||
{
|
||||
trace('Selected file: ' + selectedFile.name);
|
||||
vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}';
|
||||
state.loadVocalsFromBytes(selectedFile.bytes, charKey);
|
||||
dialogNoVocals.hidden = true;
|
||||
removeDropHandler(onDropFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// onDropFile
|
||||
addDropHandler(vocalsEntry, onDropFile);
|
||||
dialogContainer.addComponent(vocalsEntry);
|
||||
}
|
||||
|
||||
var dialogContinue:Button = dialog.findComponent('dialogContinue', Button);
|
||||
dialogContinue.onClick = (_event) ->
|
||||
{
|
||||
dialogContinue.onClick = function(_event) {
|
||||
// Dismiss
|
||||
dialog.hideDialog(DialogButton.APPLY);
|
||||
};
|
||||
|
||||
// TODO: Redo the logic for file drop handler to be more robust.
|
||||
// We need to distinguish which component the mouse is over when the file is dropped.
|
||||
|
||||
onDropFile = (path:String) ->
|
||||
{
|
||||
trace('Dropped file: ' + path);
|
||||
};
|
||||
addDropHandler(onDropFile);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and opens a dialog displaying the user guide, providing guidance and help on how to use the chart editor.
|
||||
*
|
||||
* @param state The current chart editor state.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
public static inline function openUserGuideDialog(state:ChartEditorState):Dialog
|
||||
{
|
||||
|
@ -483,8 +574,7 @@ class ChartEditorDialogHandler
|
|||
dialog.showDialog(modal);
|
||||
|
||||
state.isHaxeUIDialogOpen = true;
|
||||
dialog.onDialogClosed = (_event) ->
|
||||
{
|
||||
dialog.onDialogClosed = function(event:UIEvent) {
|
||||
state.isHaxeUIDialogOpen = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package funkin.ui.debug.charting;
|
||||
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxBasic;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.graphics.frames.FlxFramesCollection;
|
||||
import flixel.graphics.frames.FlxTileFrames;
|
||||
|
@ -14,6 +13,14 @@ import funkin.play.song.SongData.SongNoteData;
|
|||
*/
|
||||
class ChartEditorNoteSprite extends FlxSprite
|
||||
{
|
||||
/**
|
||||
* The list of available note skin to validate against.
|
||||
*/
|
||||
public static final NOTE_STYLES:Array<String> = ['Normal', 'Pixel'];
|
||||
|
||||
/**
|
||||
* The ChartEditorState this note belongs to.
|
||||
*/
|
||||
public var parentState:ChartEditorState;
|
||||
|
||||
/**
|
||||
|
@ -22,6 +29,11 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
*/
|
||||
public var noteData(default, set):SongNoteData;
|
||||
|
||||
/**
|
||||
* The name of the note style currently in use.
|
||||
*/
|
||||
public var noteStyle(get, null):String;
|
||||
|
||||
/**
|
||||
* This note is the previous sprite in a sustain chain.
|
||||
*/
|
||||
|
@ -222,14 +234,20 @@ class ChartEditorNoteSprite extends FlxSprite
|
|||
return this.childNoteSprite;
|
||||
}
|
||||
|
||||
public function playNoteAnimation()
|
||||
function get_noteStyle():String
|
||||
{
|
||||
// Fall back to 'Normal' if it's not a valid note style.
|
||||
return if (NOTE_STYLES.contains(this.parentState.currentSongNoteSkin)) this.parentState.currentSongNoteSkin else 'Normal';
|
||||
}
|
||||
|
||||
public function playNoteAnimation():Void
|
||||
{
|
||||
// Decide whether to display a note or a sustain.
|
||||
var baseAnimationName:String = 'tap';
|
||||
if (this.parentNoteSprite != null) baseAnimationName = (this.childNoteSprite != null) ? 'hold' : 'holdEnd';
|
||||
|
||||
// Play the appropriate animation for the type, direction, and skin.
|
||||
var animationName = '${baseAnimationName}${this.noteData.getDirectionName()}${this.parentState.currentSongNoteSkin}';
|
||||
var animationName:String = '${baseAnimationName}${this.noteData.getDirectionName()}${this.noteStyle}';
|
||||
|
||||
this.animation.play(animationName);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,12 @@
|
|||
package funkin.ui.haxeui;
|
||||
|
||||
import haxe.ui.containers.menus.MenuCheckBox;
|
||||
import haxe.ui.components.CheckBox;
|
||||
import haxe.ui.events.DragEvent;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import haxe.ui.containers.menus.MenuCheckBox;
|
||||
import haxe.ui.core.Component;
|
||||
import haxe.ui.core.Screen;
|
||||
import haxe.ui.events.MouseEvent;
|
||||
import haxe.ui.events.UIEvent;
|
||||
import haxe.ui.RuntimeComponentBuilder;
|
||||
import lime.app.Application;
|
||||
|
||||
class HaxeUIState extends MusicBeatState
|
||||
|
@ -23,7 +21,7 @@ class HaxeUIState extends MusicBeatState
|
|||
_componentKey = key;
|
||||
}
|
||||
|
||||
override function create()
|
||||
override function create():Void
|
||||
{
|
||||
super.create();
|
||||
|
||||
|
@ -31,7 +29,7 @@ class HaxeUIState extends MusicBeatState
|
|||
if (component != null) add(component);
|
||||
}
|
||||
|
||||
public function buildComponent(assetPath:String)
|
||||
public function buildComponent(assetPath:String):Component
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -81,15 +79,13 @@ class HaxeUIState extends MusicBeatState
|
|||
{
|
||||
if (target == null)
|
||||
{
|
||||
Screen.instance.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent)
|
||||
{
|
||||
Screen.instance.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent) {
|
||||
showContextMenu(assetPath, e.screenX, e.screenY);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
target.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent)
|
||||
{
|
||||
target.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent) {
|
||||
showContextMenu(assetPath, e.screenX, e.screenY);
|
||||
});
|
||||
}
|
||||
|
@ -98,7 +94,7 @@ class HaxeUIState extends MusicBeatState
|
|||
/**
|
||||
* Add an onClick listener to a HaxeUI menu bar item.
|
||||
*/
|
||||
function addUIClickListener(key:String, callback:MouseEvent->Void)
|
||||
function addUIClickListener(key:String, callback:MouseEvent->Void):Void
|
||||
{
|
||||
var target:Component = findComponent(key);
|
||||
if (target == null)
|
||||
|
@ -112,10 +108,24 @@ class HaxeUIState extends MusicBeatState
|
|||
}
|
||||
}
|
||||
|
||||
function setComponentText(key:String, text:String):Void
|
||||
{
|
||||
var target:Component = findComponent(key);
|
||||
if (target == null)
|
||||
{
|
||||
// Gracefully handle the case where the item can't be located.
|
||||
trace('WARN: Could not locate menu item: $key');
|
||||
}
|
||||
else
|
||||
{
|
||||
target.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an onChange listener to a HaxeUI input component such as a slider or text field.
|
||||
*/
|
||||
function addUIChangeListener(key:String, callback:UIEvent->Void)
|
||||
function addUIChangeListener(key:String, callback:UIEvent->Void):Void
|
||||
{
|
||||
var target:Component = findComponent(key);
|
||||
if (target == null)
|
||||
|
@ -179,7 +189,7 @@ class HaxeUIState extends MusicBeatState
|
|||
return component.findComponent(criteria, type, recursive, searchType);
|
||||
}
|
||||
|
||||
override function destroy()
|
||||
override function destroy():Void
|
||||
{
|
||||
if (component != null) remove(component);
|
||||
component = null;
|
||||
|
|
|
@ -5,6 +5,10 @@ import lime.utils.Bytes;
|
|||
import lime.ui.FileDialog;
|
||||
import openfl.net.FileFilter;
|
||||
import haxe.io.Path;
|
||||
#if html5
|
||||
import openfl.net.FileReference;
|
||||
import openfl.events.Event;
|
||||
#end
|
||||
|
||||
/**
|
||||
* Utilities for reading and writing files on various platforms.
|
||||
|
@ -141,8 +145,6 @@ class FileUtil
|
|||
fileDialog.open(filter, defaultPath, dialogTitle);
|
||||
return true;
|
||||
#elseif html5
|
||||
var filter = convertTypeFilter(typeFilter);
|
||||
|
||||
var onFileLoaded = function(event) {
|
||||
var loadedFileRef:FileReference = event.target;
|
||||
trace('Loaded file: ' + loadedFileRef.name);
|
||||
|
@ -157,8 +159,9 @@ class FileUtil
|
|||
}
|
||||
|
||||
var fileRef = new FileReference();
|
||||
file.addEventListener(Event.SELECT, onFileSelected);
|
||||
file.open(filter, defaultPath, dialogTitle);
|
||||
fileRef.addEventListener(Event.SELECT, onFileSelected);
|
||||
fileRef.browse(typeFilter);
|
||||
return true;
|
||||
#else
|
||||
onCancel();
|
||||
return false;
|
||||
|
@ -169,7 +172,6 @@ class FileUtil
|
|||
* Browses for a single file location, then writes the provided `haxe.io.Bytes` data and calls `onSave(path)` when done.
|
||||
* Works great on desktop and HTML5.
|
||||
*
|
||||
* @param typeFilter TODO What does this do?
|
||||
* @return Whether the file dialog was opened successfully.
|
||||
*/
|
||||
public static function saveFile(data:Bytes, ?onSave:String->Void, ?onCancel:Void->Void, ?defaultFileName:String, ?dialogTitle:String):Bool
|
||||
|
@ -191,6 +193,7 @@ class FileUtil
|
|||
if (onCancel != null) fileDialog.onCancel.add(onCancel);
|
||||
|
||||
fileDialog.save(data, filter, defaultFileName, dialogTitle);
|
||||
return true;
|
||||
#else
|
||||
onCancel();
|
||||
return false;
|
||||
|
@ -374,7 +377,11 @@ class FileUtil
|
|||
*/
|
||||
public static function appendStringToPath(path:String, data:String)
|
||||
{
|
||||
#if sys
|
||||
sys.io.File.append(path, false).writeString(data);
|
||||
#else
|
||||
throw 'Direct file writing by path not supported on this platform.';
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -410,7 +417,7 @@ class FileUtil
|
|||
{
|
||||
path = Sys.getEnv(envName);
|
||||
|
||||
if (path == "") path = null;
|
||||
if (path == '') path = null;
|
||||
if (path != null) break;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,14 +7,13 @@
|
|||
This needs to be done HERE and not via the `include` macro because `Toolkit.init()`
|
||||
reads this to build the component registry.
|
||||
-->
|
||||
<class package="haxe.ui.core" loadAll="true" />
|
||||
|
||||
<class package="haxe.ui.backend.flixel.components" loadAll="true" />
|
||||
<class package="haxe.ui.components" loadAll="true" />
|
||||
|
||||
<class package="haxe.ui.containers" loadAll="true" />
|
||||
<class package="haxe.ui.containers.menus" loadAll="true" />
|
||||
<class package="haxe.ui.containers.dialogs" loadAll="true" />
|
||||
<class package="haxe.ui.containers.menus" loadAll="true" />
|
||||
<class package="haxe.ui.containers.properties" loadAll="true" />
|
||||
<class package="haxe.ui.containers" loadAll="true" />
|
||||
<class package="haxe.ui.core" loadAll="true" />
|
||||
|
||||
<!-- Custom components. -->
|
||||
<class package="funkin.ui.haxeui.components" loadAll="true" />
|
||||
|
|
Loading…
Reference in a new issue