1
0
Fork 0
mirror of https://github.com/ninjamuffin99/Funkin.git synced 2024-12-27 07:27:12 +00:00

Merge pull request #20 from FunkinCrew/chart-editor-fixes-with-dave

Chart editor fixes with dave
This commit is contained in:
Cameron Taylor 2023-03-08 13:24:39 -05:00 committed by GitHub
commit bf7a42d143
17 changed files with 742 additions and 490 deletions

View file

@ -156,9 +156,13 @@
<haxeflag name="--macro" value="include('funkin')" /> <haxeflag name="--macro" value="include('funkin')" />
<!-- Ensure all UI components are available at runtime. --> <!-- 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.components')" />
<haxeflag name="--macro" value="include('haxe.ui.containers')" /> <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). Ensure additional class packages are available at runtime (some only really used by scripts).

View file

@ -193,7 +193,8 @@
{ {
"props": { "props": {
"max": 1000, "max": 1000,
"ignoreEmptyLines": true "ignoreEmptyLines": true,
"severity": "IGNORE"
}, },
"type": "FileLength" "type": "FileLength"
}, },
@ -232,7 +233,7 @@
}, },
{ {
"props": { "props": {
"ignoreReturnAssignments": false, "ignoreReturnAssignments": true,
"severity": "WARNING" "severity": "WARNING"
}, },
"type": "InnerAssignment" "type": "InnerAssignment"
@ -392,12 +393,13 @@
}, },
{ {
"props": { "props": {
"oldFunctionTypePolicy": "none",
"unaryOpPolicy": "none",
"intervalOpPolicy": "none",
"newFunctionTypePolicy": "around", "newFunctionTypePolicy": "around",
"ternaryOpPolicy": "around", "ternaryOpPolicy": "around",
"unaryOpPolicy": "none",
"oldFunctionTypePolicy": "around",
"boolOpPolicy": "around", "boolOpPolicy": "around",
"intervalOpPolicy": "none",
"assignOpPolicy": "around", "assignOpPolicy": "around",
"bitwiseOpPolicy": "around", "bitwiseOpPolicy": "around",
"arithmeticOpPolicy": "around", "arithmeticOpPolicy": "around",
@ -623,7 +625,9 @@
"type": "UnusedImport" "type": "UnusedImport"
}, },
{ {
"props": {}, "props": {
"severity": "WARNING"
},
"type": "UnusedLocalVar" "type": "UnusedLocalVar"
}, },
{ {

3
haxe_libraries/README.md Normal file
View file

@ -0,0 +1,3 @@
# haxe_libraries
Used by Lix

View file

@ -42,14 +42,14 @@
"name": "haxeui-core", "name": "haxeui-core",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "e5cf78d", "ref": "59157d2",
"url": "https://github.com/haxeui/haxeui-core/" "url": "https://github.com/haxeui/haxeui-core/"
}, },
{ {
"name": "haxeui-flixel", "name": "haxeui-flixel",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "f03bb6d", "ref": "d353389",
"url": "https://github.com/haxeui/haxeui-flixel" "url": "https://github.com/haxeui/haxeui-flixel"
}, },
{ {
@ -68,7 +68,7 @@
"name": "hxcodec", "name": "hxcodec",
"type": "git", "type": "git",
"dir": null, "dir": null,
"ref": "2c9a89a", "ref": "d74c2aa",
"url": "https://github.com/polybiusproxy/hxCodec" "url": "https://github.com/polybiusproxy/hxCodec"
}, },
{ {

View 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);
}
}

View file

@ -169,6 +169,7 @@ class PolymodHandler
// `polymod.*` // `polymod.*`
for (cls in ClassMacro.listClassesInPackage('polymod')) for (cls in ClassMacro.listClassesInPackage('polymod'))
{ {
if (cls == null) continue;
var className = Type.getClassName(cls); var className = Type.getClassName(cls);
Polymod.blacklistImport(className); Polymod.blacklistImport(className);
} }

View file

@ -17,8 +17,7 @@ class Module implements IPlayStateScriptedClass implements IStateChangingScripte
function set_active(value:Bool):Bool function set_active(value:Bool):Bool
{ {
this.active = value; return this.active = value;
return value;
} }
public var moduleId(default, null):String = 'UNKNOWN'; public var moduleId(default, null):String = 'UNKNOWN';

View file

@ -436,22 +436,19 @@ class AnimateAtlasCharacter extends BaseCharacter
if (!exists || x == value) return x; // early return (no need to transform) if (!exists || x == value) return x; // early return (no need to transform)
transformChildren(xTransform, value - x); // offset transformChildren(xTransform, value - x); // offset
x = value; return x = value;
return x;
} }
override function set_y(value:Float):Float override function set_y(value:Float):Float
{ {
if (exists && y != value) transformChildren(yTransform, value - y); // offset if (exists && y != value) transformChildren(yTransform, value - y); // offset
y = value; return y = value;
return y;
} }
override function set_angle(value:Float):Float override function set_angle(value:Float):Float
{ {
if (exists && angle != value) transformChildren(angleTransform, value - angle); // offset if (exists && angle != value) transformChildren(angleTransform, value - angle); // offset
angle = value; return angle = value;
return angle;
} }
override function set_alpha(value:Float):Float override function set_alpha(value:Float):Float
@ -462,43 +459,37 @@ class AnimateAtlasCharacter extends BaseCharacter
{ {
transformChildren(directAlphaTransform, value); transformChildren(directAlphaTransform, value);
} }
alpha = value; return alpha = value;
return alpha;
} }
override function set_facing(value:Int):Int override function set_facing(value:Int):Int
{ {
if (exists && facing != value) transformChildren(facingTransform, value); if (exists && facing != value) transformChildren(facingTransform, value);
facing = value; return facing = value;
return facing;
} }
override function set_flipX(value:Bool):Bool override function set_flipX(value:Bool):Bool
{ {
if (exists && flipX != value) transformChildren(flipXTransform, value); if (exists && flipX != value) transformChildren(flipXTransform, value);
flipX = value; return flipX = value;
return flipX;
} }
override function set_flipY(value:Bool):Bool override function set_flipY(value:Bool):Bool
{ {
if (exists && flipY != value) transformChildren(flipYTransform, value); if (exists && flipY != value) transformChildren(flipYTransform, value);
flipY = value; return flipY = value;
return flipY;
} }
override function set_moves(value:Bool):Bool override function set_moves(value:Bool):Bool
{ {
if (exists && moves != value) transformChildren(movesTransform, value); if (exists && moves != value) transformChildren(movesTransform, value);
moves = value; return moves = value;
return moves;
} }
override function set_immovable(value:Bool):Bool override function set_immovable(value:Bool):Bool
{ {
if (exists && immovable != value) transformChildren(immovableTransform, value); if (exists && immovable != value) transformChildren(immovableTransform, value);
immovable = value; return immovable = value;
return immovable;
} }
override function set_solid(value:Bool):Bool override function set_solid(value:Bool):Bool
@ -510,15 +501,13 @@ class AnimateAtlasCharacter extends BaseCharacter
override function set_color(value:Int):Int override function set_color(value:Int):Int
{ {
if (exists && color != value) transformChildren(gColorTransform, value); if (exists && color != value) transformChildren(gColorTransform, value);
color = value; return color = value;
return color;
} }
override function set_blend(value:BlendMode):BlendMode override function set_blend(value:BlendMode):BlendMode
{ {
if (exists && blend != value) transformChildren(blendTransform, value); if (exists && blend != value) transformChildren(blendTransform, value);
blend = value; return blend = value;
return blend;
} }
override function set_clipRect(rect:FlxRect):FlxRect override function set_clipRect(rect:FlxRect):FlxRect

View file

@ -60,7 +60,7 @@ class VanillaCutscenes
#if html5 #if html5
// Video displays OVER the FlxState. // Video displays OVER the FlxState.
vid = new FlxVideo(path); vid = new FlxVideo(path);
vid.finishCallback = finishCutscene; vid.finishCallback = finishCutscene.bind(0.5);
#else #else
// Video displays OVER the FlxState. // Video displays OVER the FlxState.
// vid = new FlxVideoSprite(0, 0); // vid = new FlxVideoSprite(0, 0);

View file

@ -126,8 +126,10 @@ class Song // implements IPlayStateScriptedClass
/** /**
* Retrieve the metadata for a specific difficulty, including the chart if it is loaded. * 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]; if (diffId == null) diffId = difficulties.keys().array()[0];

View file

@ -70,8 +70,7 @@ class Bopper extends StageProp implements IPlayStateScriptedClass
this.x += xDiff; this.x += xDiff;
this.y += yDiff; this.y += yDiff;
globalOffsets = value; return globalOffsets = value;
return value;
} }
var animOffsets(default, set):Array<Float> = [0, 0]; var animOffsets(default, set):Array<Float> = [0, 0];

View file

@ -1,16 +1,16 @@
package funkin.ui.debug.charting; package funkin.ui.debug.charting;
import flixel.FlxSprite;
import flixel.util.FlxTimer; import flixel.util.FlxTimer;
import funkin.input.Cursor; import funkin.input.Cursor;
import funkin.play.character.BaseCharacter; import funkin.play.character.BaseCharacter;
import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.character.CharacterData.CharacterDataParser;
import funkin.play.song.Song;
import funkin.play.song.SongData.SongDataParser; import funkin.play.song.SongData.SongDataParser;
import funkin.play.song.SongData.SongPlayableChar; import funkin.play.song.SongData.SongPlayableChar;
import funkin.play.song.SongData.SongTimeChange; import funkin.play.song.SongData.SongTimeChange;
import haxe.io.Path;
import haxe.ui.components.Button; import haxe.ui.components.Button;
import haxe.ui.components.DropDown; import haxe.ui.components.DropDown;
import haxe.ui.components.Image;
import haxe.ui.components.Label; import haxe.ui.components.Label;
import haxe.ui.components.Link; import haxe.ui.components.Link;
import haxe.ui.components.NumberStepper; import haxe.ui.components.NumberStepper;
@ -18,28 +18,34 @@ import haxe.ui.components.TextField;
import haxe.ui.containers.Box; import haxe.ui.containers.Box;
import haxe.ui.containers.dialogs.Dialog; import haxe.ui.containers.dialogs.Dialog;
import haxe.ui.containers.dialogs.Dialogs; import haxe.ui.containers.dialogs.Dialogs;
import haxe.ui.containers.properties.Property;
import haxe.ui.containers.properties.PropertyGrid; import haxe.ui.containers.properties.PropertyGrid;
import haxe.ui.containers.properties.PropertyGroup; import haxe.ui.containers.properties.PropertyGroup;
import haxe.ui.containers.VBox; import haxe.ui.containers.VBox;
import haxe.ui.events.MouseEvent; import haxe.ui.core.Component;
import haxe.ui.events.UIEvent; import haxe.ui.events.UIEvent;
import haxe.ui.notifications.NotificationManager;
import haxe.ui.notifications.NotificationType;
using Lambda; using Lambda;
/**
* Handles dialogs for the new Chart Editor.
*/
class ChartEditorDialogHandler class ChartEditorDialogHandler
{ {
static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT = Paths.ui('chart-editor/dialogs/about'); static final CHART_EDITOR_DIALOG_ABOUT_LAYOUT:String = Paths.ui('chart-editor/dialogs/about');
static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT = Paths.ui('chart-editor/dialogs/welcome'); static final CHART_EDITOR_DIALOG_WELCOME_LAYOUT:String = 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_UPLOAD_INST_LAYOUT:String = 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_LAYOUT:String = 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_SONG_METADATA_CHARGROUP_LAYOUT:String = 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_LAYOUT:String = 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_UPLOAD_VOCALS_ENTRY_LAYOUT:String = 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_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 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. * 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 public static function openWelcomeDialog(state:ChartEditorState, closable:Bool = true):Dialog
{ {
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable); 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. // Add handlers to the "Create From Song" section.
var linkCreateBasic:Link = dialog.findComponent('splashCreateFromSongBasic', Link); var linkCreateBasic:Link = dialog.findComponent('splashCreateFromSongBasic', Link);
linkCreateBasic.onClick = (_event) -> linkCreateBasic.onClick = function(_event) {
{ // Hide the welcome dialog
dialog.hideDialog(DialogButton.CANCEL); dialog.hideDialog(DialogButton.CANCEL);
// Create song wizard //
var uploadInstDialog = openUploadInstDialog(state, false); // Create Song Wizard
uploadInstDialog.onDialogClosed = (_event) -> //
{
// Step 1. Upload Instrumental
var uploadInstDialog:Dialog = openUploadInstDialog(state, false);
uploadInstDialog.onDialogClosed = function(_event) {
state.isHaxeUIDialogOpen = false; state.isHaxeUIDialogOpen = false;
if (_event.button == DialogButton.APPLY) if (_event.button == DialogButton.APPLY)
{ {
var songMetadataDialog = openSongMetadataDialog(state); // Step 2. Song Metadata
songMetadataDialog.onDialogClosed = (_event) -> var songMetadataDialog:Dialog = openSongMetadataDialog(state);
{ songMetadataDialog.onDialogClosed = function(_event) {
state.isHaxeUIDialogOpen = false; state.isHaxeUIDialogOpen = false;
if (_event.button == DialogButton.APPLY) 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 splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox);
var songList:Array<String> = SongDataParser.listSongIds(); var songList:Array<String> = SongDataParser.listSongIds();
for (targetSongId in songList) for (targetSongId in songList)
{ {
var songData = SongDataParser.fetchSong(targetSongId); var songData:Song = SongDataParser.fetchSong(targetSongId);
if (songData == null) continue; if (songData == null) continue;
var songName = songData.getDifficulty().songName; var songName:String = songData.getDifficulty().songName;
var linkTemplateSong:Link = new Link(); var linkTemplateSong:Link = new Link();
linkTemplateSong.text = songName; linkTemplateSong.text = songName;
linkTemplateSong.onClick = (_event) -> linkTemplateSong.onClick = function(_event) {
{
dialog.hideDialog(DialogButton.CANCEL); dialog.hideDialog(DialogButton.CANCEL);
// Load song from template // Load song from template
@ -159,78 +130,175 @@ class ChartEditorDialogHandler
return dialog; 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 public static function openUploadInstDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
{ {
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT, true, closable); 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); var instrumentalBox:Box = dialog.findComponent('instrumentalBox', Box);
instrumentalBox.onMouseOver = (_event) -> instrumentalBox.onMouseOver = function(_event) {
{
instrumentalBox.swapClass('upload-bg', 'upload-bg-hover'); instrumentalBox.swapClass('upload-bg', 'upload-bg-hover');
Cursor.cursorMode = Pointer; Cursor.cursorMode = Pointer;
} }
instrumentalBox.onMouseOut = (_event) -> instrumentalBox.onMouseOut = function(_event) {
{
instrumentalBox.swapClass('upload-bg-hover', 'upload-bg'); instrumentalBox.swapClass('upload-bg-hover', 'upload-bg');
Cursor.cursorMode = Default; Cursor.cursorMode = Default;
} }
var onDropFile:String->Void; var onDropFile:String->Void;
instrumentalBox.onClick = (_event) -> instrumentalBox.onClick = function(_event) {
{ Dialogs.openBinaryFile('Open Instrumental', [
Dialogs.openBinaryFile("Open Instrumental", [ {label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
{label: "Audio File (.ogg)", extension: "ogg"}], function(selectedFile) if (selectedFile != null)
{ {
if (selectedFile != null) if (state.loadInstrumentalFromBytes(selectedFile.bytes))
{ {
trace('Selected file: ' + selectedFile); trace('Selected file: ' + selectedFile.fullPath);
state.loadInstrumentalFromBytes(selectedFile.bytes); NotificationManager.instance.addNotification(
dialog.hideDialog(DialogButton.APPLY); {
removeDropHandler(onDropFile); 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) -> onDropFile = function(pathStr:String) {
{ var path:Path = new Path(pathStr);
trace('Dropped file: ' + path); trace('Dropped file (${path})');
state.loadInstrumentalFromPath(path); if (state.loadInstrumentalFromPath(path))
dialog.hideDialog(DialogButton.APPLY); {
removeDropHandler(onDropFile); // 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; 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 #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 #else
trace('addDropHandler not implemented for this platform'); trace('addDropHandler not implemented for this platform');
#end #end
} }
static function removeDropHandler(handler:String->Void) static function removeDropHandler(handler:String->Void):Void
{ {
#if desktop #if desktop
FlxG.stage.window.onDropFile.remove(handler); FlxG.stage.window.onDropFile.remove(handler);
#end #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 public static function openSongMetadataDialog(state:ChartEditorState):Dialog
{ {
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false); 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); var dialogSongName:TextField = dialog.findComponent('dialogSongName', TextField);
dialogSongName.onChange = (event:UIEvent) -> dialogSongName.onChange = function(event:UIEvent) {
{ var valid:Bool = event.target.text != null && event.target.text != '';
var valid = event.target.text != null && event.target.text != "";
if (valid) if (valid)
{ {
@ -245,9 +313,8 @@ class ChartEditorDialogHandler
state.currentSongMetadata.songName = null; state.currentSongMetadata.songName = null;
var dialogSongArtist:TextField = dialog.findComponent('dialogSongArtist', TextField); var dialogSongArtist:TextField = dialog.findComponent('dialogSongArtist', TextField);
dialogSongArtist.onChange = (event:UIEvent) -> dialogSongArtist.onChange = function(event:UIEvent) {
{ var valid:Bool = event.target.text != null && event.target.text != '';
var valid = event.target.text != null && event.target.text != "";
if (valid) if (valid)
{ {
@ -262,29 +329,24 @@ class ChartEditorDialogHandler
state.currentSongMetadata.artist = null; state.currentSongMetadata.artist = null;
var dialogStage:DropDown = dialog.findComponent('dialogStage', DropDown); var dialogStage:DropDown = dialog.findComponent('dialogStage', DropDown);
dialogStage.onChange = (event:UIEvent) -> dialogStage.onChange = function(event:UIEvent) {
{ if (event.data == null && event.data.id == null) return;
var valid = event.data != null && event.data.id != null;
if (event.data.id == null) return;
state.currentSongMetadata.playData.stage = event.data.id; state.currentSongMetadata.playData.stage = event.data.id;
}; };
state.currentSongMetadata.playData.stage = null; state.currentSongMetadata.playData.stage = null;
var dialogNoteSkin:DropDown = dialog.findComponent('dialogNoteSkin', DropDown); var dialogNoteSkin:DropDown = dialog.findComponent('dialogNoteSkin', DropDown);
dialogNoteSkin.onChange = (event:UIEvent) -> dialogNoteSkin.onChange = function(event:UIEvent) {
{
if (event.data.id == null) return; if (event.data.id == null) return;
state.currentSongMetadata.playData.noteSkin = event.data.id; state.currentSongMetadata.playData.noteSkin = event.data.id;
}; };
state.currentSongMetadata.playData.noteSkin = null; state.currentSongMetadata.playData.noteSkin = null;
var dialogBPM:NumberStepper = dialog.findComponent('dialogBPM', NumberStepper); var dialogBPM:NumberStepper = dialog.findComponent('dialogBPM', NumberStepper);
dialogBPM.onChange = (event:UIEvent) -> dialogBPM.onChange = function(event:UIEvent) {
{
if (event.value == null || event.value <= 0) return; 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) if (timeChanges == null || timeChanges.length == 0)
{ {
timeChanges = [new SongTimeChange(-1, 0, event.value, 4, 4, [4, 4, 4, 4])]; 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 dialogCharGrid:PropertyGrid = dialog.findComponent('dialogCharGrid', PropertyGrid);
var dialogCharAdd:Button = dialog.findComponent('dialogCharAdd', Button); var dialogCharAdd:Button = dialog.findComponent('dialogCharAdd', Button);
dialogCharAdd.onClick = (_event) -> dialogCharAdd.onClick = function(event:UIEvent) {
{
var charGroup:PropertyGroup; var charGroup:PropertyGroup;
charGroup = buildCharGroup(state, null, () -> charGroup = buildCharGroup(state, null, () -> dialogCharGrid.removeComponent(charGroup));
{
dialogCharGrid.removeComponent(charGroup);
});
dialogCharGrid.addComponent(charGroup); dialogCharGrid.addComponent(charGroup);
}; };
@ -317,20 +375,16 @@ class ChartEditorDialogHandler
dialogCharGrid.addComponent(buildCharGroup(state, 'bf', null)); dialogCharGrid.addComponent(buildCharGroup(state, 'bf', null));
var dialogContinue:Button = dialog.findComponent('dialogContinue', Button); var dialogContinue:Button = dialog.findComponent('dialogContinue', Button);
dialogContinue.onClick = (_event) -> dialogContinue.onClick = (_event) -> dialog.hideDialog(DialogButton.APPLY);
{
dialog.hideDialog(DialogButton.APPLY);
};
return dialog; 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()}'; if (groupKey == null) groupKey = 'newChar${state.currentSongMetadata.playData.playableChars.keys().count()}';
var result = state.currentSongMetadata.playData.playableChars.get(groupKey); var result = state.currentSongMetadata.playData.playableChars.get(groupKey);
@ -342,27 +396,24 @@ class ChartEditorDialogHandler
return result; return result;
} }
var moveCharGroup = (target:String) -> var moveCharGroup:String->Void = function(target:String) {
{
var charData = getCharData(); var charData = getCharData();
state.currentSongMetadata.playData.playableChars.remove(groupKey); state.currentSongMetadata.playData.playableChars.remove(groupKey);
state.currentSongMetadata.playData.playableChars.set(target, charData); state.currentSongMetadata.playData.playableChars.set(target, charData);
groupKey = target; groupKey = target;
} }
var removeGroup = () -> var removeGroup:Void->Void = function() {
{
state.currentSongMetadata.playData.playableChars.remove(groupKey); state.currentSongMetadata.playData.playableChars.remove(groupKey);
removeFunc(); removeFunc();
} }
var charData = getCharData(); var charData:SongPlayableChar = getCharData();
var charGroup:PropertyGroup = cast state.buildComponent(CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT); var charGroup:PropertyGroup = cast state.buildComponent(CHART_EDITOR_DIALOG_SONG_METADATA_CHARGROUP_LAYOUT);
var charGroupPlayer:DropDown = charGroup.findComponent('charGroupPlayer', DropDown); var charGroupPlayer:DropDown = charGroup.findComponent('charGroupPlayer', DropDown);
charGroupPlayer.onChange = (event:UIEvent) -> charGroupPlayer.onChange = function(event:UIEvent) {
{
charGroup.text = event.data.text; charGroup.text = event.data.text;
moveCharGroup(event.data.id); moveCharGroup(event.data.id);
}; };
@ -374,22 +425,19 @@ class ChartEditorDialogHandler
} }
var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown); var charGroupOpponent:DropDown = charGroup.findComponent('charGroupOpponent', DropDown);
charGroupOpponent.onChange = (event:UIEvent) -> charGroupOpponent.onChange = function(event:UIEvent) {
{
charData.opponent = event.data.id; charData.opponent = event.data.id;
}; };
charGroupOpponent.value = getCharData().opponent; charGroupOpponent.value = getCharData().opponent;
var charGroupGirlfriend:DropDown = charGroup.findComponent('charGroupGirlfriend', DropDown); var charGroupGirlfriend:DropDown = charGroup.findComponent('charGroupGirlfriend', DropDown);
charGroupGirlfriend.onChange = (event:UIEvent) -> charGroupGirlfriend.onChange = function(event:UIEvent) {
{
charData.girlfriend = event.data.id; charData.girlfriend = event.data.id;
}; };
charGroupGirlfriend.value = getCharData().girlfriend; charGroupGirlfriend.value = getCharData().girlfriend;
var charGroupRemove:Button = charGroup.findComponent('charGroupRemove', Button); var charGroupRemove:Button = charGroup.findComponent('charGroupRemove', Button);
charGroupRemove.onClick = (_event:MouseEvent) -> charGroupRemove.onClick = function(event:UIEvent) {
{
removeGroup(); removeGroup();
}; };
@ -398,22 +446,37 @@ class ChartEditorDialogHandler
return charGroup; 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 public static function openUploadVocalsDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
{ {
var charIdsForVocals = []; var charIdsForVocals:Array<String> = [];
for (charKey in state.currentSongMetadata.playData.playableChars.keys()) 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); charIdsForVocals.push(charKey);
if (charData.opponent != null) charIdsForVocals.push(charData.opponent); if (charData.opponent != null) charIdsForVocals.push(charData.opponent);
} }
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_VOCALS_LAYOUT, true, closable); 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) for (charKey in charIdsForVocals)
{ {
@ -421,49 +484,77 @@ class ChartEditorDialogHandler
var charMetadata:BaseCharacter = CharacterDataParser.fetchCharacter(charKey); var charMetadata:BaseCharacter = CharacterDataParser.fetchCharacter(charKey);
var charName:String = charMetadata.characterName; 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); 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) -> var onDropFile:String->Void = function(pathStr:String) {
{ trace('Selected file: $pathStr');
Dialogs.openBinaryFile('Open $charName Vocals', [ var path:Path = new Path(pathStr);
{label: "Audio File (.ogg)", extension: "ogg"}], function(selectedFile)
if (state.loadVocalsFromPath(path, charKey))
{ {
if (selectedFile != null) // Tell the user the load was successful.
{ NotificationManager.instance.addNotification(
trace('Selected file: ' + selectedFile.name + "~" + selectedFile.fullPath); {
vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}'; title: 'Success',
state.loadVocalsFromBytes(selectedFile.bytes); body: 'Loaded vocal track for $charName (${path.file}.${path.ext})',
removeDropHandler(onDropFile); 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); dialogContainer.addComponent(vocalsEntry);
} }
var dialogContinue:Button = dialog.findComponent('dialogContinue', Button); var dialogContinue:Button = dialog.findComponent('dialogContinue', Button);
dialogContinue.onClick = (_event) -> dialogContinue.onClick = function(_event) {
{ // Dismiss
dialog.hideDialog(DialogButton.APPLY); 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; return dialog;
} }
/** /**
* Builds and opens a dialog displaying the user guide, providing guidance and help on how to use the chart editor. * 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 public static inline function openUserGuideDialog(state:ChartEditorState):Dialog
{ {
@ -483,8 +574,7 @@ class ChartEditorDialogHandler
dialog.showDialog(modal); dialog.showDialog(modal);
state.isHaxeUIDialogOpen = true; state.isHaxeUIDialogOpen = true;
dialog.onDialogClosed = (_event) -> dialog.onDialogClosed = function(event:UIEvent) {
{
state.isHaxeUIDialogOpen = false; state.isHaxeUIDialogOpen = false;
}; };

View file

@ -1,7 +1,6 @@
package funkin.ui.debug.charting; package funkin.ui.debug.charting;
import flixel.FlxObject; import flixel.FlxObject;
import flixel.FlxBasic;
import flixel.FlxSprite; import flixel.FlxSprite;
import flixel.graphics.frames.FlxFramesCollection; import flixel.graphics.frames.FlxFramesCollection;
import flixel.graphics.frames.FlxTileFrames; import flixel.graphics.frames.FlxTileFrames;
@ -14,6 +13,14 @@ import funkin.play.song.SongData.SongNoteData;
*/ */
class ChartEditorNoteSprite extends FlxSprite 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; public var parentState:ChartEditorState;
/** /**
@ -22,6 +29,11 @@ class ChartEditorNoteSprite extends FlxSprite
*/ */
public var noteData(default, set):SongNoteData; 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. * This note is the previous sprite in a sustain chain.
*/ */
@ -222,14 +234,20 @@ class ChartEditorNoteSprite extends FlxSprite
return this.childNoteSprite; 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. // Decide whether to display a note or a sustain.
var baseAnimationName:String = 'tap'; var baseAnimationName:String = 'tap';
if (this.parentNoteSprite != null) baseAnimationName = (this.childNoteSprite != null) ? 'hold' : 'holdEnd'; if (this.parentNoteSprite != null) baseAnimationName = (this.childNoteSprite != null) ? 'hold' : 'holdEnd';
// Play the appropriate animation for the type, direction, and skin. // 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); this.animation.play(animationName);

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,12 @@
package funkin.ui.haxeui; package funkin.ui.haxeui;
import haxe.ui.containers.menus.MenuCheckBox;
import haxe.ui.components.CheckBox; import haxe.ui.components.CheckBox;
import haxe.ui.events.DragEvent; import haxe.ui.containers.menus.MenuCheckBox;
import haxe.ui.events.MouseEvent;
import haxe.ui.events.UIEvent;
import haxe.ui.RuntimeComponentBuilder;
import haxe.ui.core.Component; import haxe.ui.core.Component;
import haxe.ui.core.Screen; import haxe.ui.core.Screen;
import haxe.ui.events.MouseEvent; import haxe.ui.events.MouseEvent;
import haxe.ui.events.UIEvent;
import haxe.ui.RuntimeComponentBuilder;
import lime.app.Application; import lime.app.Application;
class HaxeUIState extends MusicBeatState class HaxeUIState extends MusicBeatState
@ -23,7 +21,7 @@ class HaxeUIState extends MusicBeatState
_componentKey = key; _componentKey = key;
} }
override function create() override function create():Void
{ {
super.create(); super.create();
@ -31,7 +29,7 @@ class HaxeUIState extends MusicBeatState
if (component != null) add(component); if (component != null) add(component);
} }
public function buildComponent(assetPath:String) public function buildComponent(assetPath:String):Component
{ {
try try
{ {
@ -81,15 +79,13 @@ class HaxeUIState extends MusicBeatState
{ {
if (target == null) 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); showContextMenu(assetPath, e.screenX, e.screenY);
}); });
} }
else else
{ {
target.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent) target.registerEvent(MouseEvent.RIGHT_CLICK, function(e:MouseEvent) {
{
showContextMenu(assetPath, e.screenX, e.screenY); showContextMenu(assetPath, e.screenX, e.screenY);
}); });
} }
@ -98,7 +94,7 @@ class HaxeUIState extends MusicBeatState
/** /**
* Add an onClick listener to a HaxeUI menu bar item. * 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); var target:Component = findComponent(key);
if (target == null) 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. * 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); var target:Component = findComponent(key);
if (target == null) if (target == null)
@ -179,7 +189,7 @@ class HaxeUIState extends MusicBeatState
return component.findComponent(criteria, type, recursive, searchType); return component.findComponent(criteria, type, recursive, searchType);
} }
override function destroy() override function destroy():Void
{ {
if (component != null) remove(component); if (component != null) remove(component);
component = null; component = null;

View file

@ -5,6 +5,10 @@ import lime.utils.Bytes;
import lime.ui.FileDialog; import lime.ui.FileDialog;
import openfl.net.FileFilter; import openfl.net.FileFilter;
import haxe.io.Path; import haxe.io.Path;
#if html5
import openfl.net.FileReference;
import openfl.events.Event;
#end
/** /**
* Utilities for reading and writing files on various platforms. * Utilities for reading and writing files on various platforms.
@ -141,8 +145,6 @@ class FileUtil
fileDialog.open(filter, defaultPath, dialogTitle); fileDialog.open(filter, defaultPath, dialogTitle);
return true; return true;
#elseif html5 #elseif html5
var filter = convertTypeFilter(typeFilter);
var onFileLoaded = function(event) { var onFileLoaded = function(event) {
var loadedFileRef:FileReference = event.target; var loadedFileRef:FileReference = event.target;
trace('Loaded file: ' + loadedFileRef.name); trace('Loaded file: ' + loadedFileRef.name);
@ -157,8 +159,9 @@ class FileUtil
} }
var fileRef = new FileReference(); var fileRef = new FileReference();
file.addEventListener(Event.SELECT, onFileSelected); fileRef.addEventListener(Event.SELECT, onFileSelected);
file.open(filter, defaultPath, dialogTitle); fileRef.browse(typeFilter);
return true;
#else #else
onCancel(); onCancel();
return false; 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. * 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. * Works great on desktop and HTML5.
* *
* @param typeFilter TODO What does this do?
* @return Whether the file dialog was opened successfully. * @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 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); if (onCancel != null) fileDialog.onCancel.add(onCancel);
fileDialog.save(data, filter, defaultFileName, dialogTitle); fileDialog.save(data, filter, defaultFileName, dialogTitle);
return true;
#else #else
onCancel(); onCancel();
return false; return false;
@ -374,7 +377,11 @@ class FileUtil
*/ */
public static function appendStringToPath(path:String, data:String) public static function appendStringToPath(path:String, data:String)
{ {
#if sys
sys.io.File.append(path, false).writeString(data); 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); path = Sys.getEnv(envName);
if (path == "") path = null; if (path == '') path = null;
if (path != null) break; if (path != null) break;
} }

View file

@ -7,14 +7,13 @@
This needs to be done HERE and not via the `include` macro because `Toolkit.init()` This needs to be done HERE and not via the `include` macro because `Toolkit.init()`
reads this to build the component registry. 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.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.dialogs" loadAll="true" />
<class package="haxe.ui.containers.menus" loadAll="true" />
<class package="haxe.ui.containers.properties" 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. --> <!-- Custom components. -->
<class package="funkin.ui.haxeui.components" loadAll="true" /> <class package="funkin.ui.haxeui.components" loadAll="true" />