diff --git a/Project.xml b/Project.xml
index a83db1677..ccf6c83a3 100644
--- a/Project.xml
+++ b/Project.xml
@@ -200,6 +200,12 @@
-->
-->
+
+
+
+
diff --git a/source/funkin/input/Cursor.hx b/source/funkin/input/Cursor.hx
index 37e819469..edd9e70f3 100644
--- a/source/funkin/input/Cursor.hx
+++ b/source/funkin/input/Cursor.hx
@@ -4,9 +4,34 @@ import openfl.utils.Assets;
import lime.app.Future;
import openfl.display.BitmapData;
+@:nullSafety
class Cursor
{
- public static var cursorMode(default, set):CursorMode;
+ /**
+ * The current cursor mode.
+ * Set this value to change the cursor graphic.
+ */
+ public static var cursorMode(default, set):Null = null;
+
+ /**
+ * Show the cursor.
+ */
+ public static inline function show():Void
+ {
+ FlxG.mouse.visible = true;
+ // Reset the cursor mode.
+ Cursor.cursorMode = Default;
+ }
+
+ /**
+ * Hide the cursor.
+ */
+ public static inline function hide():Void
+ {
+ FlxG.mouse.visible = false;
+ // Reset the cursor mode.
+ Cursor.cursorMode = null;
+ }
static final CURSOR_DEFAULT_PARAMS:CursorParams =
{
@@ -15,7 +40,7 @@ class Cursor
offsetX: 0,
offsetY: 0,
};
- static var assetCursorDefault:BitmapData = null;
+ static var assetCursorDefault:Null = null;
static final CURSOR_CROSS_PARAMS:CursorParams =
{
@@ -24,7 +49,7 @@ class Cursor
offsetX: 0,
offsetY: 0,
};
- static var assetCursorCross:BitmapData = null;
+ static var assetCursorCross:Null = null;
static final CURSOR_ERASER_PARAMS:CursorParams =
{
@@ -33,16 +58,16 @@ class Cursor
offsetX: 0,
offsetY: 0,
};
- static var assetCursorEraser:BitmapData = null;
+ static var assetCursorEraser:Null = null;
static final CURSOR_GRABBING_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-grabbing.png",
scale: 1.0,
- offsetX: 32,
+ offsetX: -8,
offsetY: 0,
};
- static var assetCursorGrabbing:BitmapData = null;
+ static var assetCursorGrabbing:Null = null;
static final CURSOR_HOURGLASS_PARAMS:CursorParams =
{
@@ -51,25 +76,34 @@ class Cursor
offsetX: 0,
offsetY: 0,
};
- static var assetCursorHourglass:BitmapData = null;
+ static var assetCursorHourglass:Null = null;
static final CURSOR_POINTER_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-pointer.png",
scale: 1.0,
- offsetX: 8,
+ offsetX: -8,
offsetY: 0,
};
- static var assetCursorPointer:BitmapData = null;
+ static var assetCursorPointer:Null = null;
static final CURSOR_TEXT_PARAMS:CursorParams =
{
graphic: "assets/images/cursor/cursor-text.png",
- scale: 1.0,
+ scale: 0.2,
offsetX: 0,
offsetY: 0,
};
- static var assetCursorText:BitmapData = null;
+ static var assetCursorText:Null = null;
+
+ static final CURSOR_TEXT_VERTICAL_PARAMS:CursorParams =
+ {
+ graphic: "assets/images/cursor/cursor-text-vertical.png",
+ scale: 0.2,
+ offsetX: 0,
+ offsetY: 0,
+ };
+ static var assetCursorTextVertical:Null = null;
static final CURSOR_ZOOM_IN_PARAMS:CursorParams =
{
@@ -78,7 +112,7 @@ class Cursor
offsetX: 0,
offsetY: 0,
};
- static var assetCursorZoomIn:BitmapData = null;
+ static var assetCursorZoomIn:Null = null;
static final CURSOR_ZOOM_OUT_PARAMS:CursorParams =
{
@@ -87,11 +121,36 @@ class Cursor
offsetX: 0,
offsetY: 0,
};
- static var assetCursorZoomOut:BitmapData = null;
+ static var assetCursorZoomOut:Null = null;
- static function set_cursorMode(value:CursorMode):CursorMode
+ static final CURSOR_CROSSHAIR_PARAMS:CursorParams =
+ {
+ graphic: "assets/images/cursor/cursor-crosshair.png",
+ scale: 1.0,
+ offsetX: -16,
+ offsetY: -16,
+ };
+ static var assetCursorCrosshair:Null = null;
+
+ static final CURSOR_CELL_PARAMS:CursorParams =
+ {
+ graphic: "assets/images/cursor/cursor-cell.png",
+ scale: 1.0,
+ offsetX: -16,
+ offsetY: -16,
+ };
+ static var assetCursorCell:Null = null;
+
+ // DESIRED CURSOR: Resize NS (vertical)
+ // DESIRED CURSOR: Resize EW (horizontal)
+ // DESIRED CURSOR: Resize NESW (diagonal)
+ // DESIRED CURSOR: Resize NWSE (diagonal)
+ // DESIRED CURSOR: Help (Cursor with question mark)
+ // DESIRED CURSOR: Menu (Cursor with menu icon)
+
+ static function set_cursorMode(value:Null):Null
{
- if (cursorMode != value)
+ if (value != null && cursorMode != value)
{
cursorMode = value;
setCursorGraphic(cursorMode);
@@ -99,16 +158,9 @@ class Cursor
return cursorMode;
}
- public static inline function show():Void
- {
- FlxG.mouse.visible = true;
- }
-
- public static inline function hide():Void
- {
- FlxG.mouse.visible = false;
- }
-
+ /**
+ * Synchronous.
+ */
static function setCursorGraphic(?value:CursorMode = null):Void
{
if (value == null)
@@ -117,6 +169,156 @@ class Cursor
return;
}
+ switch (value)
+ {
+ case Default:
+ if (assetCursorDefault == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_DEFAULT_PARAMS.graphic);
+ assetCursorDefault = bitmapData;
+ applyCursorParams(assetCursorDefault, CURSOR_DEFAULT_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorDefault, CURSOR_DEFAULT_PARAMS);
+ }
+
+ case Cross:
+ if (assetCursorCross == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_CROSS_PARAMS.graphic);
+ assetCursorCross = bitmapData;
+ applyCursorParams(assetCursorCross, CURSOR_CROSS_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorCross, CURSOR_CROSS_PARAMS);
+ }
+
+ case Eraser:
+ if (assetCursorEraser == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_ERASER_PARAMS.graphic);
+ assetCursorEraser = bitmapData;
+ applyCursorParams(assetCursorEraser, CURSOR_ERASER_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorEraser, CURSOR_ERASER_PARAMS);
+ }
+
+ case Grabbing:
+ if (assetCursorGrabbing == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_GRABBING_PARAMS.graphic);
+ assetCursorGrabbing = bitmapData;
+ applyCursorParams(assetCursorGrabbing, CURSOR_GRABBING_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorGrabbing, CURSOR_GRABBING_PARAMS);
+ }
+
+ case Hourglass:
+ if (assetCursorHourglass == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_HOURGLASS_PARAMS.graphic);
+ assetCursorHourglass = bitmapData;
+ applyCursorParams(assetCursorHourglass, CURSOR_HOURGLASS_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorHourglass, CURSOR_HOURGLASS_PARAMS);
+ }
+
+ case Pointer:
+ if (assetCursorPointer == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_POINTER_PARAMS.graphic);
+ assetCursorPointer = bitmapData;
+ applyCursorParams(assetCursorPointer, CURSOR_POINTER_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorPointer, CURSOR_POINTER_PARAMS);
+ }
+
+ case Text:
+ if (assetCursorText == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_TEXT_PARAMS.graphic);
+ assetCursorText = bitmapData;
+ applyCursorParams(assetCursorText, CURSOR_TEXT_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorText, CURSOR_TEXT_PARAMS);
+ }
+
+ case ZoomIn:
+ if (assetCursorZoomIn == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_ZOOM_IN_PARAMS.graphic);
+ assetCursorZoomIn = bitmapData;
+ applyCursorParams(assetCursorZoomIn, CURSOR_ZOOM_IN_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorZoomIn, CURSOR_ZOOM_IN_PARAMS);
+ }
+
+ case ZoomOut:
+ if (assetCursorZoomOut == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_ZOOM_OUT_PARAMS.graphic);
+ assetCursorZoomOut = bitmapData;
+ applyCursorParams(assetCursorZoomOut, CURSOR_ZOOM_OUT_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorZoomOut, CURSOR_ZOOM_OUT_PARAMS);
+ }
+
+ case Crosshair:
+ if (assetCursorCrosshair == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_CROSSHAIR_PARAMS.graphic);
+ assetCursorCrosshair = bitmapData;
+ applyCursorParams(assetCursorCrosshair, CURSOR_CROSSHAIR_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorCrosshair, CURSOR_CROSSHAIR_PARAMS);
+ }
+
+ case Cell:
+ if (assetCursorCell == null)
+ {
+ var bitmapData:BitmapData = Assets.getBitmapData(CURSOR_CELL_PARAMS.graphic);
+ assetCursorCell = bitmapData;
+ applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
+ }
+ else
+ {
+ applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
+ }
+
+ default:
+ setCursorGraphic(null);
+ }
+ }
+
+ /**
+ * Asynchronous.
+ */
+ static function loadCursorGraphic(?value:CursorMode = null):Void
+ {
+ if (value == null)
+ {
+ FlxG.mouse.unload();
+ return;
+ }
+
switch (value)
{
case Default:
@@ -127,6 +329,7 @@ class Cursor
assetCursorDefault = bitmapData;
applyCursorParams(assetCursorDefault, CURSOR_DEFAULT_PARAMS);
});
+ future.onError(onCursorError.bind(Default));
}
else
{
@@ -141,6 +344,7 @@ class Cursor
assetCursorCross = bitmapData;
applyCursorParams(assetCursorCross, CURSOR_CROSS_PARAMS);
});
+ future.onError(onCursorError.bind(Cross));
}
else
{
@@ -155,6 +359,7 @@ class Cursor
assetCursorEraser = bitmapData;
applyCursorParams(assetCursorEraser, CURSOR_ERASER_PARAMS);
});
+ future.onError(onCursorError.bind(Eraser));
}
else
{
@@ -169,6 +374,7 @@ class Cursor
assetCursorGrabbing = bitmapData;
applyCursorParams(assetCursorGrabbing, CURSOR_GRABBING_PARAMS);
});
+ future.onError(onCursorError.bind(Grabbing));
}
else
{
@@ -183,6 +389,7 @@ class Cursor
assetCursorHourglass = bitmapData;
applyCursorParams(assetCursorHourglass, CURSOR_HOURGLASS_PARAMS);
});
+ future.onError(onCursorError.bind(Hourglass));
}
else
{
@@ -197,6 +404,7 @@ class Cursor
assetCursorPointer = bitmapData;
applyCursorParams(assetCursorPointer, CURSOR_POINTER_PARAMS);
});
+ future.onError(onCursorError.bind(Pointer));
}
else
{
@@ -211,6 +419,7 @@ class Cursor
assetCursorText = bitmapData;
applyCursorParams(assetCursorText, CURSOR_TEXT_PARAMS);
});
+ future.onError(onCursorError.bind(Text));
}
else
{
@@ -225,6 +434,7 @@ class Cursor
assetCursorZoomIn = bitmapData;
applyCursorParams(assetCursorZoomIn, CURSOR_ZOOM_IN_PARAMS);
});
+ future.onError(onCursorError.bind(ZoomIn));
}
else
{
@@ -239,14 +449,45 @@ class Cursor
assetCursorZoomOut = bitmapData;
applyCursorParams(assetCursorZoomOut, CURSOR_ZOOM_OUT_PARAMS);
});
+ future.onError(onCursorError.bind(ZoomOut));
}
else
{
applyCursorParams(assetCursorZoomOut, CURSOR_ZOOM_OUT_PARAMS);
}
+ case Crosshair:
+ if (assetCursorCrosshair == null)
+ {
+ var future:Future = Assets.loadBitmapData(CURSOR_CROSSHAIR_PARAMS.graphic);
+ future.onComplete(function(bitmapData:BitmapData) {
+ assetCursorCrosshair = bitmapData;
+ applyCursorParams(assetCursorCrosshair, CURSOR_CROSSHAIR_PARAMS);
+ });
+ future.onError(onCursorError.bind(Crosshair));
+ }
+ else
+ {
+ applyCursorParams(assetCursorCrosshair, CURSOR_CROSSHAIR_PARAMS);
+ }
+
+ case Cell:
+ if (assetCursorCell == null)
+ {
+ var future:Future = Assets.loadBitmapData(CURSOR_CELL_PARAMS.graphic);
+ future.onComplete(function(bitmapData:BitmapData) {
+ assetCursorCell = bitmapData;
+ applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
+ });
+ future.onError(onCursorError.bind(Cell));
+ }
+ else
+ {
+ applyCursorParams(assetCursorCell, CURSOR_CELL_PARAMS);
+ }
+
default:
- setCursorGraphic(null);
+ loadCursorGraphic(null);
}
}
@@ -254,6 +495,11 @@ class Cursor
{
FlxG.mouse.load(graphic, params.scale, params.offsetX, params.offsetY);
}
+
+ static function onCursorError(cursorMode:CursorMode, error:String):Void
+ {
+ trace("Failed to load cursor graphic for cursor mode " + cursorMode + ": " + error);
+ }
}
// https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
@@ -268,6 +514,8 @@ enum CursorMode
Text;
ZoomIn;
ZoomOut;
+ Crosshair;
+ Cell;
}
/**
diff --git a/source/funkin/play/song/SongMigrator.hx b/source/funkin/play/song/SongMigrator.hx
index bb8718bb7..f33d9bbe9 100644
--- a/source/funkin/play/song/SongMigrator.hx
+++ b/source/funkin/play/song/SongMigrator.hx
@@ -179,7 +179,7 @@ class SongMigrator
songMetadata.playData.playableChars = {};
try
{
- Reflect.setField(songMetadata.playData.playableChars, songData.song.player1, new SongPlayableChar('', songData.song.player2));
+ songMetadata.playData.playableChars.set(songData.song.player1, new SongPlayableChar('', songData.song.player2));
}
catch (e)
{
diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx
index 7391c3d16..eb75e31c5 100644
--- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx
@@ -6,6 +6,7 @@ import funkin.util.SerializerUtil;
import funkin.play.song.SongData.SongChartData;
import funkin.play.song.SongData.SongMetadata;
import flixel.util.FlxTimer;
+import funkin.ui.haxeui.components.FunkinLink;
import funkin.util.SortUtil;
import funkin.input.Cursor;
import funkin.play.character.BaseCharacter;
@@ -134,7 +135,7 @@ class ChartEditorDialogHandler
continue;
}
- var linkTemplateSong:Link = new Link();
+ var linkTemplateSong:Link = new FunkinLink();
linkTemplateSong.text = songName;
linkTemplateSong.onClick = function(_event) {
dialog.hideDialog(DialogButton.CANCEL);
@@ -306,6 +307,7 @@ class ChartEditorDialogHandler
if (state.loadInstrumentalFromBytes(selectedFile.bytes))
{
trace('Selected file: ' + selectedFile.fullPath);
+ #if !mac
NotificationManager.instance.addNotification(
{
title: 'Success',
@@ -313,6 +315,7 @@ class ChartEditorDialogHandler
type: NotificationType.Success,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
+ #end
dialog.hideDialog(DialogButton.APPLY);
removeDropHandler(onDropFile);
@@ -321,6 +324,7 @@ class ChartEditorDialogHandler
{
trace('Failed to load instrumental (${selectedFile.fullPath})');
+ #if !mac
NotificationManager.instance.addNotification(
{
title: 'Failure',
@@ -328,6 +332,7 @@ class ChartEditorDialogHandler
type: NotificationType.Error,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
+ #end
}
}
});
@@ -339,6 +344,7 @@ class ChartEditorDialogHandler
if (state.loadInstrumentalFromPath(path))
{
// Tell the user the load was successful.
+ #if !mac
NotificationManager.instance.addNotification(
{
title: 'Success',
@@ -346,6 +352,7 @@ class ChartEditorDialogHandler
type: NotificationType.Success,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
+ #end
dialog.hideDialog(DialogButton.APPLY);
removeDropHandler(onDropFile);
@@ -362,6 +369,7 @@ class ChartEditorDialogHandler
}
// Tell the user the load was successful.
+ #if !mac
NotificationManager.instance.addNotification(
{
title: 'Failure',
@@ -369,6 +377,7 @@ class ChartEditorDialogHandler
type: NotificationType.Error,
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
});
+ #end
}
};
@@ -383,6 +392,15 @@ class ChartEditorDialogHandler
handler:(String->Void)
}> = [];
+ /**
+ * Add a callback for when a file is dropped on a component.
+ *
+ * On OS X you can’t drop on the application window, but rather only the app icon
+ * (either in the dock while running or the icon on the hard drive) so this must be disabled
+ * and UI updated appropriately.
+ * @param component
+ * @param handler
+ */
static function addDropHandler(component:Component, handler:String->Void):Void
{
#if desktop
@@ -647,7 +665,11 @@ class ChartEditorDialogHandler
var vocalsEntryLabel:Null