Merge branch 'CodenameCrew:main' into patch-1

This commit is contained in:
Kolze
2025-07-27 14:15:56 +02:00
committed by GitHub
581 changed files with 16266 additions and 4429 deletions
+24 -19
View File
@@ -26,50 +26,55 @@ class DebugOptions extends TreeMenu {
class DebugOptionsScreen extends OptionsScreen {
public override function new() {
super("Debug Options", "Use this menu to change debug options.");
prefix = "DebugOptions.";
super(translate("title"), translate("desc"), prefix);
#if windows
add(new TextOption(
"Show Console",
"Select this to show the debug console, which contains log information about the game.",
getName("showConsole"),
getDesc("showConsole"),
function() {
NativeAPI.allocConsole();
}));
#end
add(new Checkbox(
"Resizable Editors",
"If checked, this will allow the editors to render beyond the base 1280x720 resolution of FNF (allowing for more detail and space...)",
getName("editorsResizable"),
getDesc("editorsResizable"),
"editorsResizable"));
add(new Checkbox(
"Editor SFXs",
"If checked, will play sound effects when working on editors (ex: will play SFXs when checking checkboxes...)",
getName("bypassEditorsResize"),
getDesc("bypassEditorsResize"),
"bypassEditorsResize"));
add(new Checkbox(
getName("editorSFX"),
getDesc("editorSFX"),
"editorSFX"));
add(new Checkbox(
"Pretty Print",
"If checked, the saved files from the editor will be formatted to be easily viewable (does not apply to XMLs...)",
getName("editorPrettyPrint"),
getDesc("editorPrettyPrint"),
"editorPrettyPrint"));
add(new Checkbox(
"Intensive Blur",
"If checked, will use more intensive blur that may be laggier but look better.",
getName("intensiveBlur"),
getDesc("intensiveBlur"),
"intensiveBlur"));
add(new Checkbox(
"Editor Autosaves",
"If checked, this will autosave your files in the editor, with the settings listed below.",
getName("charterAutoSaves"),
getDesc("charterAutoSaves"),
"charterAutoSaves"));
add(new NumOption(
"Autosaving Time",
"This controls how often the editor will autosave your file (in seconds...)",
getName("charterAutoSaveTime"),
getDesc("charterAutoSaveTime"),
60, 60*10, 30,
"charterAutoSaveTime"
));
add(new NumOption(
"Save Warning Time",
"This controls how long the editor will warn you before it autosaves (in seconds..., 0 to disable)",
getName("charterAutoSaveWarningTime"),
getDesc("charterAutoSaveWarningTime"),
0, 15, 1,
"charterAutoSaveWarningTime"
));
add(new Checkbox(
"Autosaves Folder",
"If checked, this will autosave your file in a separate folder with a time stamp instead of overriding your current file. (song/autosaves/)",
getName("charterAutoSavesSeparateFolder"),
getDesc("charterAutoSavesSeparateFolder"),
"charterAutoSavesSeparateFolder"));
}
}
+14 -4
View File
@@ -6,7 +6,13 @@ import flixel.math.FlxPoint;
class EditorPicker extends MusicBeatSubstate {
public var bg:FlxSprite;
// Name is for backwards compatibility, don't use it, use id instead
public var options:Array<Editor> = [
{
name: "Alphabet Editor",
id: "alphabet",
state: funkin.editors.alphabet.AlphabetSelection
},
{
name: "Chart Editor",
id: "chart",
@@ -22,7 +28,7 @@ class EditorPicker extends MusicBeatSubstate {
id: "stage",
state: funkin.editors.stage.StageSelection
},
#if debug
#if (debug || debug_ui)
{
name: "UI Debug State",
id: "uiDebug",
@@ -74,7 +80,8 @@ class EditorPicker extends MusicBeatSubstate {
optionHeight = FlxG.height / options.length;
for(k=>o in options) {
var spr = new EditorPickerOption(o.name, o.id, optionHeight);
var visualName = (o.id != null) ? TU.translate("editor." + o.id + ".name") : o.name;
var spr = new EditorPickerOption(visualName, o.id, optionHeight);
spr.y = k * optionHeight;
add(spr);
sprites.push(spr);
@@ -176,13 +183,16 @@ class EditorPickerOption extends FlxTypedSpriteGroup<FlxSprite> {
FlxG.mouse.visible = true;
iconSpr = new FlxSprite();
iconSpr.loadGraphic(Paths.image('editors/icons/$iconID'));
if(iconID != null)
iconSpr.loadGraphic(Paths.image('editors/icons/$iconID'));
else
iconSpr.exists = false;
iconSpr.antialiasing = true;
iconSpr.setUnstretchedGraphicSize(110, 110, false);
iconSpr.x = 25 + ((height - iconSpr.width) / 2);
iconSpr.y = (height - iconSpr.height) / 2;
label = new Alphabet(25 + iconSpr.width + 25, 0, name, true);
label = new Alphabet(25 + iconSpr.width + 25, 0, name, "bold");
label.y = (height - label.height) / 2;
selectionBG = new FlxSprite().makeGraphic(1, 1, -1);
+7 -6
View File
@@ -7,6 +7,7 @@ import funkin.options.*;
class EditorTreeMenu extends TreeMenu {
public var bg:FlxBackdrop;
public var bgType:String = "default";
public var bgMovement:FlxPoint = new FlxPoint();
public override function create() {
super.create();
@@ -18,16 +19,16 @@ class EditorTreeMenu extends TreeMenu {
bg = new FlxBackdrop();
bg.loadGraphic(Paths.image('editors/bgs/${bgType}'));
bg.antialiasing = true;
setBackgroundRotation(-5);
add(bg);
}
public inline function setBackgroundRotation(rotation:Float) {
bg.rotation = rotation;
bg.velocity.set(85, 0).degrees = bg.rotation;
}
public override function exit() {
FlxG.switchState(new MainMenuState());
}
public override function update(elapsed:Float) {
super.update(elapsed);
bg.x += elapsed * 125;
bg.y += elapsed * 125;
}
}
+78
View File
@@ -0,0 +1,78 @@
package funkin.editors;
import funkin.backend.assets.ModsFolderLibrary;
import flixel.FlxState;
class ModConfigWarning extends UIState {
public static var hadPopup:Bool = false;
var library:ModsFolderLibrary = null;
var goToState:Class<FlxState>;
public static inline var defaultModConfigText =
'[Common]
MOD_NAME="YOUR MOD NAME HERE"
MOD_DESCRIPTION="YOUR MOD DESCRIPTION HERE"
MOD_AUTHOR="YOU/YOUR TEAM HERE"
MOD_VERSION="YOUR MOD\'S VERSION HERE"
# DO NOT EDIT!! this is used to check for version compatibility!
MOD_API_VERSION=1
MOD_DOWNLOAD_LINK="YOUR MOD PAGE LINK HERE"
# Not supported yet
;MOD_ICON64="path/to/icon64.png"
;MOD_ICON32="path/to/icon32.png"
;MOD_ICON16="path/to/icon16.png"
MOD_ICON="path/to/icon.png"
[Flags]
DISABLE_WARNING_SCREEN=true
# Set this to false if you want to bring back the warning state (prior to 1.0.0)
# NOTE: Beta warning state has been renamed from BetaWarningState.hx to WarningState.hx
[Discord]
MOD_DISCORD_CLIENT_ID=""
MOD_DISCORD_LOGO_KEY=""
MOD_DISCORD_LOGO_TEXT=""';
public function new(library:ModsFolderLibrary, ?goToState:Class<FlxState>) {
super();
this.library = library;
this.goToState = goToState != null ? goToState : funkin.menus.TitleState;
}
override function createPost() {
super.createPost();
hadPopup = true;
var substate = new UIWarningSubstate("Missing mod folder configuration!", "Your mod is currently missing a mod config file!\n\nWould you like to automatically generate one?\n\n(PS: If this is not your mod please disable Developer mode to stop this popup from appearing.)", [
{
label: "Not Now",
color: 0x969533,
onClick: function (_) {
MusicBeatState.skipTransOut = MusicBeatState.skipTransIn = false;
FlxG.switchState(cast Type.createInstance(goToState, []));
}
},
{
label: "Yes",
onClick: function(_) {
var path = '${library.folderPath}/data/config/modpack.ini';
CoolUtil.safeSaveFile(path, defaultModConfigText);
openSubState(new UIWarningSubstate("Created mod config!", "Your mod config file has been created at " + path + "!", [
{
label: "Ok",
onClick: function (_) {
MusicBeatState.skipTransOut = MusicBeatState.skipTransIn = false;
FlxG.switchState(cast Type.createInstance(goToState, []));
}
},
], false));
}
}
], false);
openSubState(substate);
}
}
+6 -6
View File
@@ -21,15 +21,15 @@ class SaveWarning {
if (warningFunc != null) warningFunc(closingWindow);
if (FlxG.state != null && FlxG.state is UIState) {
FlxG.state.openSubState(new UIWarningSubstate("Unsaved Changes!", "Your changes will be lost if you don't save them. (Can't be recovered)\n\n\nWould you like to Cancel?",
FlxG.state.openSubState(new UIWarningSubstate(TU.translate("saveWarning.warningTitle"), TU.translate("saveWarning.warningDesc"),
[
{
label: "Cancel",
label: TU.translate("saveWarning.cancel"),
color: 0x969533,
onClick: function (_) {if (closingWindow) WindowUtils.resetClosing();}
},
{
label: closingWindow ? "Exit Game" : "Exit To Menu",
label: closingWindow ? TU.translate("saveWarning.exitGame") : TU.translate("saveWarning.exitToMenu"),
color: 0x969533,
onClick: function(_) {
if (!closingWindow) {
@@ -37,12 +37,12 @@ class SaveWarning {
if (closingWindow) WindowUtils.resetClosing();
} else {
WindowUtils.preventClosing = false; WindowUtils.resetClosing();
Sys.exit(0);
openfl.system.System.exit(0);
}
}
},
{
label: closingWindow ? "Save & Exit Game" : "Save & Exit To Menu",
label: closingWindow ? TU.translate("saveWarning.saveAndExitGame") : TU.translate("saveWarning.saveAndExitToMenu"),
color: 0x969533,
onClick: function(_) {
if (saveFunc != null) saveFunc();
@@ -51,7 +51,7 @@ class SaveWarning {
if (closingWindow) WindowUtils.resetClosing();
} else {
WindowUtils.preventClosing = false; WindowUtils.resetClosing();
Sys.exit(0);
openfl.system.System.exit(0);
}
}
}
@@ -0,0 +1,499 @@
package funkin.editors.alphabet;
import flixel.math.FlxPoint;
import flixel.util.FlxColor;
import funkin.backend.system.framerate.Framerate;
import funkin.backend.utils.XMLUtil.AnimData;
import funkin.editors.ui.UIContextMenu.UIContextMenuOption;
import funkin.game.Character;
import haxe.xml.Access;
import haxe.xml.Printer;
@:access(funkin.menus.ui.Alphabet)
class AlphabetEditor extends UIState {
static var __typeface:String;
public static var instance(get, null):AlphabetEditor;
private static inline function get_instance()
return FlxG.state is AlphabetEditor ? cast FlxG.state : null;
public var topMenu:Array<UIContextMenuOption>;
public var topMenuSpr:UITopMenu;
public var uiGroup:FlxTypedGroup<FlxSprite> = new FlxTypedGroup<FlxSprite>();
var editorCamera:FlxCamera;
var uiCamera:FlxCamera;
public function new(typeface:String) {
super();
if (typeface != null) __typeface = typeface;
}
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("editor.alphabet." + id, args);
public var brokenWarning:UIText;
public var tape:Alphabet;
public var bigLetter:Alphabet;
public var curLetter:Int = 0;
public var targetX:Float = 0;
public var queueReorder:Bool = false;
public var componentList:UIButtonList<ComponentButton>;
public var infoWindow:GlyphInfoWindow;
public var curSelectedComponent:AlphabetComponent = null;
public var curSelectedData:AlphabetLetterData = null;
public var outlineIdx:Int = -1;
public var defaultTmr:Float = 0.0;
public var charsForDefault:Array<Array<String>> = [];
public override function create() {
super.create();
WindowUtils.suffix = " (" + translate("name") + ")";
SaveWarning.selectionClass = AlphabetSelection;
SaveWarning.saveFunc = () -> {_file_save(null);};
topMenu = [
{
label: translate("topBar.file"),
childs: [
{
label: translate("file.save"),
keybind: [CONTROL, S],
onSelect: _file_save
},
{
label: translate("file.saveAs"),
keybind: [CONTROL, SHIFT, S],
onSelect: _file_saveas
},
null,
{
label: translate("file.exit"),
onSelect: _file_exit
}
]
// TODO: add more options
},
{
label: translate("topBar.edit"),
childs: [
{
label: translate("edit.undo"),
// TODO: add undo
},
{
label: translate("edit.redo"),
// TODO: add redo
},
{
label: "Edit Main Data", // TODO: add translations
onSelect: _edit_main
}
]
},
{
label: translate("topBar.glyph"),
childs: [
{
label: translate("glyph.newGlyph"),
//onSelect: _glyph_new
},
{
label: translate("glyph.editGlyph"),
//onSelect: _glyph_edit
},
{
label: translate("glyph.deleteGlyph"),
//onSelect: _glyph_delete
}
]
},
{
label: translate("topBar.view"),
childs: [
{
label: translate("view.zoomIn"),
keybind: [CONTROL, NUMPADPLUS],
//onSelect: _view_zoomin
},
{
label: translate("view.zoomOut"),
keybind: [CONTROL, NUMPADMINUS],
//onSelect: _view_zoomout
},
{
label: translate("view.resetZoom"),
keybind: [CONTROL, NUMPADZERO],
//onSelect: _view_zoomreset
},
]
},
{
label: "Tape",
childs: [
{
label: "Move Tape Left",
keybind: [LEFT],
onSelect: _tape_left
},
{
label: "Move Tape Right",
keybind: [RIGHT],
onSelect: _tape_right
}
]
}
];
editorCamera = FlxG.camera;
uiCamera = new FlxCamera();
uiCamera.bgColor = 0;
FlxG.cameras.add(uiCamera, false);
var bg = new FlxSprite(0, 0).makeSolid(Std.int(FlxG.width + 100), Std.int(FlxG.height + 100), 0xFF7f7f7f);
bg.cameras = [editorCamera];
bg.scrollFactor.set();
bg.screenCenter();
add(bg);
topMenuSpr = new UITopMenu(topMenu);
topMenuSpr.cameras = uiGroup.cameras = [uiCamera];
tape = new Alphabet(0, 70, "", __typeface);
tape.alignment = CENTER;
tape.renderMode = MONOSPACE;
add(tape);
bigLetter = new Alphabet(0, 0, "", "<SKIP>");
bigLetter.copyData(tape);
bigLetter.alignment = CENTER;
bigLetter.scale.set(4, 4);
// TODO: fix the offset issues, when not using updateHitbox
bigLetter.updateHitbox();
bigLetter.screenCenter();
add(bigLetter);
tape.x = targetX;
brokenWarning = new UIText(0, 550, FlxG.width, "", 32);
brokenWarning.alignment = CENTER;
brokenWarning.color = 0xFFFF6969;
add(brokenWarning);
infoWindow = new GlyphInfoWindow();
uiGroup.add(infoWindow);
componentList = new UIButtonList<ComponentButton>(0, 720 - 170 - 30, 230, 170, "Components:", FlxPoint.get(230, 50), FlxPoint.get(0, 0), 0);
componentList.dragCallback = (button, oldID, newID) -> {
queueReorder = true; // not do it for every button reordered
}
componentList.addButton.callback = () -> {
curSelectedComponent = {
anim: "",
x: 0,
y: 0,
shouldRotate: false,
angle: 0,
sin: 0,
cos: 1,
scaleX: 1,
scaleY: 1,
hasColorMode: false,
colorMode: bigLetter.colorMode
};
curSelectedData.components.push(curSelectedComponent);
var newButton = new ComponentButton(curSelectedComponent);
componentList.add(newButton);
newButton.ID = componentList.buttons.members.length - 1; // readjust it because UIButtonList doesnt generate the id properly
findOutline();
infoWindow.updateInfo();
}
updateTape();
uiGroup.add(componentList);
add(topMenuSpr);
add(uiGroup);
if(Framerate.isLoaded) {
Framerate.fpsCounter.alpha = 0.4;
Framerate.memoryCounter.alpha = 0.4;
Framerate.codenameBuildField.alpha = 0.4;
}
DiscordUtil.call("onEditorLoaded", ["Alphabet Editor", __typeface]);
}
override function destroy() {
super.destroy();
if(Framerate.isLoaded) {
Framerate.fpsCounter.alpha = 1;
Framerate.memoryCounter.alpha = 1;
Framerate.codenameBuildField.alpha = 1;
}
}
public override function update(elapsed:Float) {
super.update(elapsed);
if (queueReorder) {
queueReorder = false;
var outlines = [];
var newList = [];
for (button in componentList.buttons.members) {
var com = button.component;
if (com.outIndex != null) {
var out = curSelectedData.components[com.outIndex];
out.refIndex = newList.length;
com.outIndex = outlines.length;
outlines.push(out);
}
newList.push(com);
}
curSelectedData.components = outlines.concat(newList);
findOutline();
for (i in 0...curSelectedData.components.length) {
var anim = bigLetter.text + i;
bigLetter.animation.remove(anim);
tape.animation.remove(anim);
}
}
if (currentFocus == null) {
if(FlxG.keys.justPressed.ANY)
UIUtil.processShortcuts(topMenu);
if(curSelectedComponent != null) {
if(FlxG.keys.pressed.K) {
curSelectedComponent.y -= 100 * elapsed;
}
if(FlxG.keys.pressed.I) {
curSelectedComponent.y += 100 * elapsed;
}
if(FlxG.keys.pressed.L) {
curSelectedComponent.x -= 100 * elapsed;
}
if(FlxG.keys.pressed.J) {
curSelectedComponent.x += 100 * elapsed;
}
}
}
var lastIdx = Math.floor(defaultTmr);
defaultTmr += elapsed * 0.5;
if (lastIdx != (lastIdx = Math.floor(defaultTmr))) {
tape.text = "";
for (def in charsForDefault)
tape.text += def[Std.int(lastIdx % def.length)] + " ";
tape.text += tape.manualLetters.join(" ");
}
tape.x = lerp(tape.x, targetX, 0.25);
}
function updateTape() {
charsForDefault = [[], [], []];
for (i in 33...127) {
var letter = String.fromCharCode(i);
if (tape.manualLetters.contains(letter)) continue;
var data = tape.fastGetData(letter);
for (i => com in data.components) {
if (tape.fastGetLetterAnim(letter, data, com, i) == null)
continue;
}
if (i >= 'A'.code && i <= 'Z'.code && tape.defaults[CaseMode.UPPER] != null)
charsForDefault[CaseMode.UPPER].push(letter);
else if (i >= 'a'.code && i <= 'z'.code && tape.defaults[CaseMode.LOWER] != null)
charsForDefault[CaseMode.LOWER].push(letter);
else if (tape.defaults[CaseMode.NONE] != null)
charsForDefault[CaseMode.NONE].push(letter);
}
var i = charsForDefault.length - 1;
while (i >= 0) {
if (charsForDefault[i].length <= 0)
charsForDefault.splice(i, 1);
--i;
}
tape.text = "";
for (def in charsForDefault)
tape.text += def[Math.floor(defaultTmr % def.length)] + " ";
tape.text += tape.manualLetters.join(" ");
changeLetter(0);
}
function changeLetter(inc:Int) {
curLetter = CoolUtil.positiveModuloInt(curLetter + inc, tape.manualLetters.length + charsForDefault.length);
targetX = FlxG.width * 0.5 - tape.defaultAdvance * (0.5 + curLetter * 2);
bigLetter.text = (curLetter < charsForDefault.length) ? charsForDefault[curLetter][0] : tape.manualLetters[curLetter - charsForDefault.length];
bigLetter.updateHitbox();
bigLetter.screenCenter();
checkForFailed();
while (componentList.buttons.members.length > 0)
componentList.remove(componentList.buttons.members[0]);
for (i in curSelectedData.startIndex...curSelectedData.components.length) {
var newButton = new ComponentButton(curSelectedData.components[i]);
componentList.add(newButton);
newButton.ID = componentList.buttons.members.length - 1; // readjust it because UIButtonList doesnt generate the id properly
}
curSelectedComponent = (curSelectedData.components.length > 0) ? curSelectedData.components[curSelectedData.startIndex] : null;
findOutline();
infoWindow.updateInfo();
}
public function findOutline() {
outlineIdx = -1;
for (i in curSelectedData.startIndex...curSelectedData.components.length) {
var com = curSelectedData.components[i];
if (com.outIndex != null)
outlineIdx = com.outIndex;
if (com == curSelectedComponent)
break;
}
++outlineIdx;
}
public function checkForFailed() {
bigLetter.failedLetters.remove(bigLetter.text);
curSelectedData = bigLetter.fastGetData(bigLetter.text);
brokenWarning.text = "";
bigLetter.draw();
if (!bigLetter.failedLetters.contains(bigLetter.text))
return false;
for (i in 0...curSelectedData.components.length) {
var com = curSelectedData.components[i];
if (!bigLetter.animation.exists(bigLetter.text + Std.string(i)))
brokenWarning.text += " Unable to find: " + com.anim + "\n";
}
return true;
}
function _tape_left(_) {
changeLetter(-1);
}
function _tape_right(_) {
changeLetter(1);
}
function _file_save(_) {
#if sys
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/data/alphabet/${__typeface}.xml',
""//alphabet.buildXML()
);
#else
_file_saveas(_);
#end
}
function _file_saveas(_) {
openSubState(new SaveSubstate(""/*alphabet.buildXML()*/, {
defaultSaveFile: '${__typeface}.xml'
}));
}
function _file_exit(_) {
/*if (undos.unsaved) SaveWarning.triggerWarning();
else */FlxG.switchState(new AlphabetSelection());
}
function _edit_main(_) {
FlxG.state.openSubState(new AlphabetMainDataScreen());
}
}
/*
/===============\
| Components |
|===============|
|[ Component 1 ]|
|[ Component 2 ]|
|[ Component 3 ]|
|[ Component 4 ]|
|[ Component 5 ]|
|[ Add component]|
\===============/
*/
class ComponentButton extends UIButton {
public var component:AlphabetComponent;
public var selected:Bool = false;
public var deleteButton:UIButton;
public var deleteIcon:FlxSprite;
public function new(component:AlphabetComponent) {
super(0, 0, component.anim, function() {
AlphabetEditor.instance.curSelectedComponent = component;
AlphabetEditor.instance.findOutline();
AlphabetEditor.instance.infoWindow.updateInfo();
}, 230, 50);
this.component = component;
deleteButton = new UIButton(bWidth - 32 - 10, 0, "", function() {
var data = AlphabetEditor.instance.curSelectedData;
var outlineDrop = (component.outIndex != null) ? 1 : 0;
var componentIndex = data.components.indexOf(component);
for (i in 0...data.components.length) {
var nextCompon = data.components[i];
var anim = AlphabetEditor.instance.bigLetter.text + i;
AlphabetEditor.instance.bigLetter.animation.remove(anim);
AlphabetEditor.instance.tape.animation.remove(anim);
if (i > componentIndex && nextCompon.outIndex != null) {
--data.components[nextCompon.outIndex].refIndex;
nextCompon.outIndex -= outlineDrop;
}
}
if (component.outIndex != null) {
data.components.splice(component.outIndex, 1);
--data.startIndex;
}
data.components.remove(component);
AlphabetEditor.instance.curSelectedComponent = (AlphabetEditor.instance.curSelectedComponent == component) ? null : AlphabetEditor.instance.curSelectedComponent;
AlphabetEditor.instance.findOutline();
AlphabetEditor.instance.infoWindow.updateInfo();
AlphabetEditor.instance.componentList.remove(this);
}, 32);
deleteButton.color = FlxColor.RED;
deleteButton.autoAlpha = false;
members.push(deleteButton);
deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 8).loadGraphic(Paths.image('editors/delete-button'));
deleteIcon.antialiasing = false;
members.push(deleteIcon);
}
override function update(elapsed:Float) {
super.update(elapsed);
deleteButton.y = y + 10;
deleteIcon.x = deleteButton.x + (15/2);
deleteIcon.y = deleteButton.y + 8;
}
}
@@ -0,0 +1,214 @@
package funkin.editors.alphabet;
import flixel.math.FlxRect;
import funkin.editors.alphabet.AlphabetEditor;
import funkin.menus.ui.Alphabet;
using StringTools;
// TODO: add translations
class AlphabetMainDataScreen extends UISubstateWindow {
var bigLetter:Alphabet;
var tape:Alphabet;
public var saveButton:UIButton;
public var closeButton:UIButton;
public var helpButton:UIButton;
public var fpsStepper:UINumericStepper;
public var advanceStepper:UINumericStepper;
public var lineGapStepper:UINumericStepper;
public var caseDropdown:UIDropDown;
public var colorDropdown:UIDropDown;
public var regTextBox:UITextBox;
public var upperTextBox:UITextBox;
public var lowerTextBox:UITextBox;
public var targetPercent:Float = 0.0;
public var helpBG:UISliceSprite;
public var helpTxt:UIText;
public var helpScroll:UIScrollBar;
public override function create() {
bigLetter = AlphabetEditor.instance.bigLetter;
tape = AlphabetEditor.instance.tape;
// temporary until translated
var helpDesc =
"FPS - Frames per second. You get it.
Advance - Actually a backup advance value for missing characters and spaces.
Line Gap - How many pixels to move down when a new line is created.
Force Casing - Whether to force the text casing to uppercase, lowercase, or neither.
Color Mode - How to color the alphabet.
TINT muliplies the channels, perfect for white letters.
OFFSETS adds to the channels, perfect for black letters.
NONE simply disables coloring.
Backup Prefixes - What prefix to use if a glyph is not predefined.
Uppercase and Lowercase will default to regular if it is left blank.
LETTER will be substituted with the provided glyph.
UPPERLETTER will be substituted with the uppercase glyph.
LOWERLETTER will be substituted with the lowercase glyph.
UPPERLETTER and LOWERLETTTER can be used in any backup prefix.";
winTitle = "Alphabet Properties";
winWidth = 420; winHeight = 520;
super.create();
function addLabelOn(ui:UISprite, text:String)
add(new UIText(ui.x, ui.y - 24, 0, text));
var title:UIText;
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Edit Main Alphabet Data", 28));
fpsStepper = new UINumericStepper(title.x, title.y + title.height + 38, AlphabetEditor.instance.bigLetter.fps, 1, 2, 0.01, null, 120);
add(fpsStepper);
addLabelOn(fpsStepper, "FPS");
advanceStepper = new UINumericStepper(fpsStepper.x + 140, fpsStepper.y, AlphabetEditor.instance.bigLetter.defaultAdvance, 1, 2, 0, null, 120);
add(advanceStepper);
addLabelOn(advanceStepper, "Advance");
lineGapStepper = new UINumericStepper(advanceStepper.x + 140, advanceStepper.y, AlphabetEditor.instance.bigLetter.lineGap, 1, 2, 0, null, 120);
add(lineGapStepper);
addLabelOn(lineGapStepper, "Line Gap");
caseDropdown = new UIDropDown(fpsStepper.x, fpsStepper.y + fpsStepper.bHeight + 50, 206, 32, ["NONE", "UPPER", "LOWER"], tape.forceCase);
add(caseDropdown);
addLabelOn(caseDropdown, "Force Casing");
colorDropdown = new UIDropDown(caseDropdown.x + caseDropdown.bWidth + 20, caseDropdown.y, 206, 32, ["TINT", "OFFSETS", "NONE"], tape.colorMode);
add(colorDropdown);
addLabelOn(colorDropdown, "Color Mode");
var regPrefix = (tape.defaults[0] != null) ? tape.defaults[0].components[0].anim : "";
regTextBox = new UITextBox(caseDropdown.x, caseDropdown.y + caseDropdown.bHeight + 50, regPrefix, 368);
add(regTextBox);
addLabelOn(regTextBox, "Backup Prefix (Regular)");
var upperPrefix = (tape.defaults[1] != null) ? tape.defaults[1].components[0].anim : "";
upperTextBox = new UITextBox(regTextBox.x, regTextBox.y + regTextBox.bHeight + 35, upperPrefix, 368);
add(upperTextBox);
addLabelOn(upperTextBox, "Backup Prefix (Uppercase)");
var lowerPrefix = (tape.defaults[2] != null) ? tape.defaults[2].components[0].anim : "";
lowerTextBox = new UITextBox(upperTextBox.x, upperTextBox.y + upperTextBox.bHeight + 35, lowerPrefix, 368);
add(lowerTextBox);
addLabelOn(lowerTextBox, "Backup Prefix (Lowercase)");
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
saveInfo();
close();
}, 125);
add(saveButton);
closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() {
close();
}, 125);
add(closeButton);
closeButton.color = 0xFFFF0000;
closeButton.x -= closeButton.bWidth;
helpButton = new UIButton(closeButton.x - 20, closeButton.y, "?", function() {
targetPercent = 1.0 - targetPercent;
}, 32);
add(helpButton);
helpButton.x -= helpButton.bWidth;
helpBG = new UISliceSprite(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y, 340, windowSpr.bHeight, 'editors/ui/context-bg');
helpBG.alpha = 0.0;
add(helpBG);
helpTxt = new UIText(helpBG.x + 20, helpBG.y + 20, helpBG.bWidth - 60, helpDesc);
helpTxt.clipRect = new FlxRect(0, 0, helpTxt.width, helpBG.bHeight - 40);
helpTxt.alpha = 0.0;
add(helpTxt);
helpScroll = new UIScrollBar(helpBG.x + helpBG.bWidth - 30, helpBG.y + 20, (helpTxt.height - helpTxt.clipRect.height), 0, (helpTxt.clipRect.height / helpTxt.height), 20, helpBG.bHeight - 40);
helpScroll.size *= helpScroll.height;
helpScroll.start -= helpScroll.size * 0.5;
helpScroll.alpha = 0.0;
helpScroll.onChange = function(v) {
helpTxt.clipRect.y = v;
helpTxt.offset.y = v;
helpTxt.clipRect = helpTxt.clipRect;
helpScroll.start = v - helpScroll.size * 0.5;
}
add(helpScroll);
}
override function update(elapsed:Float) {
super.update(elapsed);
helpBG.alpha = FlxMath.lerp(helpBG.alpha, targetPercent, elapsed * 15);
helpBG.x = windowSpr.x + windowSpr.bWidth - 20 + 40 * helpBG.alpha;
helpTxt.alpha = helpBG.alpha;
helpTxt.x = helpBG.x + 20;
helpScroll.alpha = helpScroll.thumb.alpha = helpScroll.thumbIcon.alpha = helpBG.alpha;
helpScroll.x = helpBG.x + helpBG.bWidth - 30;
}
public function saveInfo() {
@:privateAccess fpsStepper.__onChange(fpsStepper.label.text);
@:privateAccess advanceStepper.__onChange(advanceStepper.label.text);
@:privateAccess lineGapStepper.__onChange(lineGapStepper.label.text);
bigLetter.fps = tape.fps = fpsStepper.value;
bigLetter.defaultAdvance = tape.defaultAdvance = advanceStepper.value;
bigLetter.lineGap = tape.lineGap = lineGapStepper.value;
bigLetter.forceCase = tape.forceCase = caseDropdown.index;
bigLetter.colorMode = tape.colorMode = colorDropdown.index;
// give the letter loading another chance
tape.animation.destroyAnimations();
bigLetter.animation.destroyAnimations();
tape.failedLetters.splice(1, tape.failedLetters.length - 1);
setDefault(0, regTextBox.label.text);
setDefault(1, upperTextBox.label.text);
setDefault(2, lowerTextBox.label.text);
}
function setDefault(index:Int, name:String) {
tape.loaded[index].splice(0, tape.loaded[index].length);
if (name.trim() == "") {
tape.defaults[index] = null;
return;
}
if (tape.defaults[index] != null) {
tape.defaults[index].components[0].anim = name;
trace(tape.defaults[index].components[0].anim);
return;
}
final newData:AlphabetLetterData = {
isDefault: true,
advance: 0.0,
advanceEmpty: true,
components: [{
anim: name,
x: 0.0,
y: 0.0,
scaleX: 1.0,
scaleY: 1.0,
shouldRotate: false,
angle: 0.0,
cos: 1.0,
sin: 0.0,
hasColorMode: false,
colorMode: 0
}],
startIndex: 0
};
tape.defaults[index] = newData;
}
}
@@ -0,0 +1,89 @@
package funkin.editors.alphabet;
import haxe.xml.Access;
import funkin.game.Character;
import funkin.options.OptionsScreen;
import funkin.options.type.IconOption;
import funkin.options.type.NewOption;
import funkin.options.type.TextOption;
import funkin.options.type.OptionType;
import haxe.io.Path;
class AlphabetSelection extends EditorTreeMenu
{
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("editor.alphabet.selection." + id, args);
public override function create()
{
bgType = "charter";
super.create();
var modsList:Array<String> = [];
for(file in Paths.getFolderContent('data/alphabet/', true, BOTH)) // mods ? MODS : BOTH
if (Path.extension(file) == "xml") modsList.push(CoolUtil.getFilename(file));
var list:Array<OptionType> = [
for (typeface in modsList)
new AlphabetIconOption(typeface, translate("acceptTypeface"), typeface, function() {
FlxG.switchState(new AlphabetEditor(typeface));
})
];
var newChar = translate("newTypeface");
list.insert(0, new NewOption(newChar, newChar, function() {
openSubState(new UIWarningSubstate(translate("warnings.notImplemented-title"), translate("warnings.notImplemented-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}}
]));
}));
main = new OptionsScreen(TU.translate("editor.alphabet.name"), translate("desc"), list);
DiscordUtil.call("onEditorTreeLoaded", ["Alphabet Editor"]);
}
override function createPost() {
super.createPost();
main.changeSelection(1);
}
}
class AlphabetIconOption extends TextOption {
public var iconSpr:FlxSprite;
public function new(name:String, desc:String, typeface:String, callback:Void->Void) {
super(name, desc, callback);
var xml = Xml.parse(Assets.getText(Paths.xml('alphabet/$typeface'))).firstElement();
var spritesheet = null;
for(node in xml.elements()) {
if (node.nodeName == "spritesheet") {
spritesheet = node.firstChild().nodeValue;
break;
}
}
var useColorOffsets = xml.get("useColorOffsets").getDefault("false") == "true";
// todo fix crash if invalid spritesheet;
iconSpr = new FlxSprite();
iconSpr.frames = Paths.getFrames(spritesheet);
iconSpr.antialiasing = true;
var frameToUse = iconSpr.frames.frames[0];
for(frame in iconSpr.frames.frames) {
if (frame.name.toUpperCase().startsWith("A")) {
frameToUse = frame;
break;
}
}
iconSpr.frame = frameToUse;
if(useColorOffsets) {
iconSpr.colorTransform.color = -1;
}
iconSpr.setPosition(90 - iconSpr.width - 20, (__text.height - iconSpr.height) / 2);
add(iconSpr);
}
}
@@ -0,0 +1,274 @@
package funkin.editors.alphabet;
import flixel.math.FlxAngle;
class GlyphInfoWindow extends UIWindow {
public var prefixBox:UITextBox;
public var xBox:UINumericStepper; // LIVE
public var yBox:UINumericStepper; // MJWF
public var scaleXBox:UINumericStepper;
public var scaleYBox:UINumericStepper;
public var angleBox:UINumericStepper;
public var colorModeDrop:UIDropDown;
public var outlineTitle:UIText;
public var outlineSep:UISprite;
public var outlineCheck:UICheckbox;
public var outlineBoxTitle:UIText;
public var outlineBox:UITextBox;
public var outlineOffTitle:UIText;
public var outlineXBox:UINumericStepper;
public var outlineYBox:UINumericStepper;
public var targetPercent:Float = 0;
public var outlinePercent:Float = 0;
public var outlineAlphaMults:Array<Float>;
public var outlineItems:Array<FlxSprite>;
public var initialY:Array<Float> = [];
var compon(get, never):AlphabetComponent;
var data(get, never):AlphabetLetterData;
public function new() {
var width = 360;
var height = 255;
var margin = 30;
var itemMargin = 15; // there was already a buncha variables so i thought why not follow
var labelOffset = 24;
super(FlxG.width - width - margin, FlxG.height - height - margin, width, height, "Glyph Info");
function addLabelOn(ui:UISprite, text:String) {
var title = new UIText(ui.x, ui.y - labelOffset, 0, text);
members.push(title);
return title;
}
prefixBox = new UITextBox(x + itemMargin, y + itemMargin + labelOffset * 2, "", 330);
prefixBox.onChange = function(val) {
var index = data.components.indexOf(compon);
compon.anim = val;
var anim = AlphabetEditor.instance.bigLetter.text + Std.string(index);
AlphabetEditor.instance.bigLetter.animation.remove(anim);
AlphabetEditor.instance.tape.animation.remove(anim);
AlphabetEditor.instance.checkForFailed();
}
members.push(prefixBox);
addLabelOn(prefixBox, "Animation Prefix");
xBox = new UINumericStepper(prefixBox.x, prefixBox.y + prefixBox.bHeight + itemMargin + labelOffset, 0, 1, 2, null, null, 80);
xBox.onChange = valueSet.bind(xBox, function(val) { // kinda dumb but blame cne ui
compon.x = val;
});
members.push(xBox);
members.push(new UIText(xBox.x + xBox.bWidth - 2, xBox.y + 9, 0, ",", 22));
yBox = new UINumericStepper(xBox.x + xBox.bWidth + itemMargin, xBox.y, 0, 1, 2, null, null, 80);
yBox.onChange = valueSet.bind(yBox, function(val) { // kinda dumb but blame cne ui
compon.y = val;
});
members.push(yBox);
addLabelOn(xBox, "Offset (x,y)");
scaleXBox = new UINumericStepper(yBox.x + yBox.bWidth + itemMargin * 2, yBox.y, 1, 0.1, 2, null, null, 80);
scaleXBox.onChange = valueSet.bind(scaleXBox, function(val) { // kinda dumb but blame cne ui
compon.scaleX = val;
if (outlineCheck.checked)
data.components[compon.outIndex].scaleX = val;
});
members.push(scaleXBox);
members.push(new UIText(scaleXBox.x + scaleXBox.bWidth - 2, scaleXBox.y + 9, 0, ",", 22));
scaleYBox = new UINumericStepper(scaleXBox.x + scaleXBox.bWidth + itemMargin, scaleXBox.y, 1, 0.1, 2, null, null, 80);
scaleYBox.onChange = valueSet.bind(scaleYBox, function(val) { // kinda dumb but blame cne ui
compon.scaleY = val;
if (outlineCheck.checked)
data.components[compon.outIndex].scaleY = val;
});
members.push(scaleYBox);
addLabelOn(scaleXBox, "Scale (x,y)");
angleBox = new UINumericStepper(scaleYBox.x + scaleYBox.bWidth + itemMargin * 2, scaleYBox.y, 0, 1, 2, null, null, 80);
angleBox.onChange = valueSet.bind(angleBox, function(val) { // kinda dumb but blame cne ui
compon.shouldRotate = val != 0;
compon.angle = val;
compon.cos = Math.cos(val * FlxAngle.TO_RAD);
compon.sin = Math.sin(val * FlxAngle.TO_RAD);
if (outlineCheck.checked) {
data.components[compon.outIndex].shouldRotate = compon.shouldRotate;
data.components[compon.outIndex].angle = compon.angle;
data.components[compon.outIndex].cos = compon.cos;
data.components[compon.outIndex].sin = compon.sin;
}
});
members.push(angleBox);
addLabelOn(angleBox, "Angle");
colorModeDrop = new UIDropDown(xBox.x, xBox.y + xBox.bHeight + itemMargin + labelOffset, 145, 32, ["TINT", "OFFSETS", "NONE"]);
colorModeDrop.onChange = function(mode) {
compon.colorMode = mode;
@:privateAccess compon.hasColorMode = mode != AlphabetEditor.instance.tape.colorMode;
if (outlineCheck.checked) {
data.components[compon.outIndex].colorMode = mode;
@:privateAccess data.components[compon.outIndex].hasColorMode = compon.hasColorMode;
}
};
members.push(colorModeDrop);
addLabelOn(colorModeDrop, "Color Mode");
outlineCheck = new UICheckbox(colorModeDrop.x + colorModeDrop.bWidth + itemMargin * 2, colorModeDrop.y + 4, "Use Outline?");
outlineCheck.onChecked = function(val) {
targetPercent = val ? 1 : 0;
for (i in 0...data.components.length) {
var anim = AlphabetEditor.instance.bigLetter.text + i;
AlphabetEditor.instance.bigLetter.animation.remove(anim);
AlphabetEditor.instance.tape.animation.remove(anim);
}
if (val) {
compon.outIndex = AlphabetEditor.instance.outlineIdx;
var newOutline:AlphabetComponent = {
refIndex: data.components.indexOf(compon),
anim: outlineBox.label.text,
x: outlineXBox.value,
y: outlineYBox.value,
scaleX: compon.scaleX,
scaleY: compon.scaleY,
shouldRotate: compon.shouldRotate,
angle: compon.angle,
cos: compon.cos,
sin: compon.sin,
hasColorMode: compon.hasColorMode,
colorMode: compon.colorMode
};
data.components.insert(AlphabetEditor.instance.outlineIdx, newOutline);
++data.startIndex;
} else {
compon.outIndex = null;
data.components.splice(AlphabetEditor.instance.outlineIdx - 1, 1);
--data.startIndex;
}
}
members.push(outlineCheck);
outlineTitle = new UIText(colorModeDrop.x, colorModeDrop.y + colorModeDrop.bHeight + itemMargin, 0, "Outline Data");
members.push(outlineTitle);
outlineSep = new UISprite(x + itemMargin, colorModeDrop.y + colorModeDrop.bHeight + itemMargin + 19);
outlineSep.makeSolid(width - itemMargin * 2, 1, 0x40FFFFFF);
members.push(outlineSep);
outlineBox = new UITextBox(colorModeDrop.x, colorModeDrop.y + colorModeDrop.bHeight + itemMargin + labelOffset * 2 + 3, "", 190);
outlineBox.onChange = function(val) {
data.components[compon.outIndex].anim = val;
var anim = AlphabetEditor.instance.bigLetter.text + Std.string(compon.outIndex);
AlphabetEditor.instance.bigLetter.animation.remove(anim);
AlphabetEditor.instance.tape.animation.remove(anim);
AlphabetEditor.instance.checkForFailed();
}
members.push(outlineBox);
outlineBoxTitle = addLabelOn(outlineBox, "Prefix");
outlineXBox = new UINumericStepper(outlineBox.x + outlineBox.bWidth + itemMargin * 2, outlineBox.y, 0, 1, 2, null, null, 80);
outlineXBox.onChange = valueSet.bind(outlineXBox, function(val) { // kinda dumb but blame cne ui
if (outlineCheck.checked)
data.components[compon.outIndex].x = val;
});
members.push(outlineXBox);
members.push(new UIText(outlineXBox.x + outlineXBox.bWidth - 2, outlineXBox.y + 9, 0, ",", 22));
outlineYBox = new UINumericStepper(outlineXBox.x + outlineXBox.bWidth + itemMargin, outlineXBox.y, 0, 1, 2, null, null, 80);
outlineYBox.onChange = valueSet.bind(outlineYBox, function(val) { // kinda dumb but blame cne ui
if (outlineCheck.checked)
data.components[compon.outIndex].y = val;
});
members.push(outlineYBox);
outlineOffTitle = addLabelOn(outlineXBox, "Offset (x,y)");
for (mem in members) {
if (mem is FlxObject)
initialY.push(cast (mem, FlxObject).y);
}
outlineAlphaMults = [1.0, 0.9, 0.5, 0.15, 0.15, 0.5, 0.15, 0.15, 0.15, 0.15];
outlineItems = [outlineTitle, outlineSep, outlineBoxTitle, outlineBox, outlineBox.label, outlineOffTitle, outlineXBox, outlineXBox.label, outlineYBox, outlineYBox.label];
}
override public function update(elapsed:Float) {
super.update(elapsed);
outlinePercent = FlxMath.lerp(outlinePercent, targetPercent, elapsed * 10);
bHeight = Std.int(255 + 105 * outlinePercent);
y = FlxG.height - bHeight - 30;
for (i in 0...outlineItems.length)
outlineItems[i].alpha = outlinePercent * FlxMath.lerp(outlineAlphaMults[i], 1, outlinePercent * outlinePercent) * alpha;
var yIdxOff = 0;
for (i in 0...members.length) {
if (members[i] is FlxObject)
cast (members[i], FlxObject).y = initialY[i - yIdxOff] - (bHeight - 255);
else
++yIdxOff;
}
}
public function updateInfo() {
var com = compon;
if (com == null) {
for (item in members) {
if (item is UISprite)
cast(item, UISprite).selectable = false;
}
colorModeDrop.dropButton.selectable = false;
return;
}
prefixBox.label.text = com.anim;
xBox.value = com.x;
yBox.value = com.y;
scaleXBox.value = com.scaleX;
scaleYBox.value = com.scaleY;
angleBox.value = com.angle;
@:privateAccess colorModeDrop.index = com.hasColorMode ? com.colorMode : AlphabetEditor.instance.tape.colorMode;
@:privateAccess colorModeDrop.label.text = colorModeDrop.options[colorModeDrop.index];
outlineCheck.checked = com.outIndex != null;
outlineBox.label.text = "";
outlineXBox.value = 0;
outlineYBox.value = 0;
targetPercent = 0;
if (com.outIndex != null) {
var out = data.components[com.outIndex];
outlineBox.label.text = out.anim;
outlineXBox.value = out.x;
outlineYBox.value = out.y;
targetPercent = 1;
}
for (item in members) {
if (item is UISprite)
cast(item, UISprite).selectable = true;
}
colorModeDrop.dropButton.selectable = true;
}
function valueSet(item:UINumericStepper, func:Dynamic, text:String) {
@:privateAccess item.__onChange(text);
func(item.value);
}
function get_compon() {
return AlphabetEditor.instance.curSelectedComponent;
}
function get_data() {
return AlphabetEditor.instance.curSelectedData;
}
}
@@ -48,6 +48,9 @@ class CharacterAnimButton extends UIButton {
public var foldableButtons:Array<FlxSprite> = [];
public var closed:Bool = true;
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("characterEditor.characterAnim." + id, args);
public function new(x:Float, y:Float, animData:AnimData, parent:CharacterAnimsWindow) {
this.anim = animData.name;
this.data = animData;
@@ -76,13 +79,13 @@ class CharacterAnimButton extends UIButton {
nameTextBox.onChange = (newName:String) -> {this.changeName(newName);};
members.push(nameTextBox);
foldableButtons.push(nameTextBox);
addLabelOn(nameTextBox, "Name", 12);
addLabelOn(nameTextBox, translate("name"), 12);
animTextBox = new UIAutoCompleteTextBox(nameTextBox.x + 100, nameTextBox.y, animData.anim, 156, 22, false, true);
animTextBox.onChange = (newAnim:String) -> {this.changeAnim(newAnim);};
members.push(animTextBox);
foldableButtons.push(animTextBox);
addLabelOn(animTextBox, "Animation", 12);
addLabelOn(animTextBox, translate("anim"), 12);
positionXStepper = new UINumericStepper(animTextBox.x, animTextBox.y+32+18, animData.x, 0.001, 2, null, null, 64, 22, true);
positionXStepper.onChange = (text:String) -> {
@@ -91,7 +94,7 @@ class CharacterAnimButton extends UIButton {
};
members.push(positionXStepper);
foldableButtons.push(positionXStepper);
addLabelOn(positionXStepper, "Position (X,Y)", 12);
addLabelOn(positionXStepper, translate("position"), 12);
members.push(XYComma = new UIText(positionXStepper.x+104-32+0, positionXStepper.y + 9, 0, ",", 18));
foldableButtons.push(XYComma);
@@ -111,7 +114,7 @@ class CharacterAnimButton extends UIButton {
};
members.push(fpsStepper);
foldableButtons.push(fpsStepper);
addLabelOn(fpsStepper, "FPS", 12);
addLabelOn(fpsStepper, translate("fps"), 12);
loopedCheckbox = new UICheckbox(fpsStepper.x + 82 - 32 + 26, fpsStepper.y, "Looping?", animData.loop, 0, true);
loopedCheckbox.onChecked = (newLooping:Bool) -> {this.changeLooping(newLooping);};
@@ -126,7 +129,7 @@ class CharacterAnimButton extends UIButton {
}
members.push(indicesTextBox);
foldableButtons.push(indicesTextBox);
addLabelOn(indicesTextBox, "Indices (frames)", 12);
addLabelOn(indicesTextBox, translate("indices"), 12);
animIcon = new FlxSprite(x-(10+16), y+8).loadGraphic(Paths.image("editors/character/anim-icons"), true, 16, 12);
animIcon.animation.add("play", [0]);
@@ -421,10 +424,12 @@ class CharacterAnimButton extends UIButton {
public function toggleGhost() {
if (valid && parent.ghosts.indexOf(anim) == -1) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARACTER_GHOSTENABLE_SOUND));
parent.ghosts.push(anim);
ghostIcon.animation.play("alive", true);
ghostIcon.color = 0xFFFFFFFF;
} else {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_CHARACTER_GHOSTDISABLE_SOUND));
parent.ghosts.remove(anim);
ghostIcon.animation.play("dead", true);
ghostIcon.color = 0xFFADADAD;
@@ -99,6 +99,7 @@ class CharacterAnimsWindow extends UIButtonList<CharacterAnimButton> {
displayAnimsFramesList.remove(name);
public function deleteAnimation(button:CharacterAnimButton, addToUndo:Bool = true) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DELETE_SOUND));
if (buttons.members.length <= 1) return;
if (character.getAnimName() == button.anim)
@:privateAccess CharacterEditor.instance._animation_down(null);
@@ -112,13 +113,14 @@ class CharacterAnimsWindow extends UIButtonList<CharacterAnimButton> {
}
public function generateAnimation() {
var animName:String = "New Anim";
var newAnim = TU.translate("characterEditor.characterAnim.defaultAnimName");
var animName:String = newAnim;
var animNames:Array<String> = character.getNameList();
var newAnimCount:Int = 0;
while (animNames.indexOf(animName) != -1) {
newAnimCount++;
animName = 'New Anim - $newAnimCount';
animName = '$newAnim - $newAnimCount';
}
if (__autoCompleteAnims.length <= 0)
@@ -15,13 +15,16 @@ class CharacterCreationScreen extends UISubstateWindow {
public var saveButton:UIButton;
public var closeButton:UIButton;
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("characterCreationScreen." + id, args);
public function new(?onSave:(String, ImageSaveData, Xml)->Void) {
super();
if (onSave != null) this.onSave = onSave;
}
public override function create() {
winTitle = "Creating New Character";
winTitle = translate("win-title");
winWidth = 360;
winHeight = 520;
@@ -37,24 +40,24 @@ class CharacterCreationScreen extends UISubstateWindow {
characterNameTextBox = new UITextBox(windowSpr.x + 20, windowSpr.y + 30 + 16 + 20, "character", 320);
characterNameTextBox.onChange = (_) -> {checkRequired();};
add(characterNameTextBox);
addLabelOn(characterNameTextBox, "Character File Name").applyMarkup(
"Character File Name $* Required$",
addLabelOn(characterNameTextBox, "").applyMarkup(
translate("charName"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
imageExplorer = new UIImageExplorer(characterNameTextBox.x, characterNameTextBox.y + 30 + 16 + 20, null, 320, 58, (_, _) -> {onLoadImage();}, "images/characters");
add(imageExplorer);
addLabelOn(imageExplorer, "Character Image File").applyMarkup(
"Character Image File $* Required$",
addLabelOn(imageExplorer, "").applyMarkup(
translate("charImgName"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
imageExplorer.maxSize.y -= 100;
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
close();
createCharacter();
}, 125);
add(saveButton);
closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", function() {
closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.cancel"), function() {
close();
}, 125);
add(closeButton);
@@ -65,74 +65,77 @@ class CharacterEditor extends UIState {
if (character != null) __character = character;
}
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("characterEditor." + id, args);
public override function create() {
super.create();
undos = new UndoList<CharacterEditorChange>();
WindowUtils.suffix = " (Character Editor)";
WindowUtils.suffix = " (" + TU.translate("editor.character.name") + ")";
SaveWarning.selectionClass = CharacterSelection;
SaveWarning.saveFunc = () -> {_file_save(null);};
topMenu = [
{
label: "File",
label: translate("topBar.file"),
childs: [
{
label: "New",
label: translate("file.new"),
onSelect: _file_new,
},
null,
{
label: "Save",
label: translate("file.save"),
keybind: [CONTROL, S],
onSelect: _file_save,
},
{
label: "Save As...",
label: translate("file.saveAs"),
keybind: [CONTROL, SHIFT, S],
onSelect: _file_saveas,
},
null,
{
label: "Exit",
label: translate("file.exit"),
onSelect: _file_exit
}
]
},
{
label: "Edit",
label: translate("topBar.edit"),
childs: [
{
label: "Copy Offset",
label: translate("edit.copyOffset"),
keybind: [CONTROL, C],
onSelect: _edit_copy_offset
},
{
label: "Paste Offset",
label: translate("edit.pasteOffset"),
keybind: [CONTROL, V],
onSelect: _edit_paste_offset
},
null,
{
label: "Undo",
label: translate("edit.undo"),
keybind: [CONTROL, Z],
onSelect: _edit_undo
},
{
label: "Redo",
label: translate("edit.redo"),
keybinds: [[CONTROL, Y], [CONTROL, SHIFT, Z]],
onSelect: _edit_redo
},
null,
{
label: "Edit information",
label: translate("edit.editInfo"),
color: 0xFF959829, icon: 4,
onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2;},
onSelect: _edit_info
},
{
label: "Edit sprite",
label: translate("edit.editSprite"),
color: 0xFF959829, icon: 4,
onCreate: function (button:UIContextMenuOptionSpr) {button.label.offset.x = button.icon.offset.x = -2;},
onSelect: _edit_sprite
@@ -140,128 +143,128 @@ class CharacterEditor extends UIState {
]
},
{
label: "Offsets",
label: translate("topBar.offset"),
childs: [
{
label: "Move Left",
label: translate("offset.moveLeft"),
keybind: [LEFT],
onSelect: _offsets_left,
},
{
label: "Move Up",
label: translate("offset.moveUp"),
keybind: [UP],
onSelect: _offsets_up,
},
{
label: "Move Down",
label: translate("offset.moveDown"),
keybind: [DOWN],
onSelect: _offsets_down,
},
{
label: "Move Right",
label: translate("offset.moveRight"),
keybind: [RIGHT],
onSelect: _offsets_right,
},
null,
{
label: "Move Extra Left",
label: translate("offset.moveExtraLeft"),
keybind: [SHIFT, LEFT],
onSelect: _offsets_extra_left,
},
{
label: "Move Extra Up",
label: translate("offset.moveExtraUp"),
keybind: [SHIFT, UP],
onSelect: _offsets_extra_up,
},
{
label: "Move Extra Down",
label: translate("offset.moveExtraDown"),
keybind: [SHIFT, DOWN],
onSelect: _offsets_extra_down,
},
{
label: "Move Extra Right",
label: translate("offset.moveExtraRight"),
keybind: [SHIFT, RIGHT],
onSelect: _offsets_extra_right,
},
null,
{
label: "Drag Offsets With Mouse?",
label: translate("offset.dragOffsetsMouse"),
onSelect: _offsets_drag_offsets_mouse,
icon: Options.characterDragging ? 1 : 0
},
{
label: "Clear Offsets",
label: translate("offset.clearOffsets"),
keybind: [CONTROL, R],
onSelect: _offsets_clear,
}
]
},
{
label: "Stage",
label: translate("topBar.stage"),
childs: buildStagesUI()
},
{
label: "View",
label: translate("topBar.view"),
childs: [
{
label: "Zoom in",
label: translate("view.zoomIn"),
keybind: [CONTROL, NUMPADPLUS],
onSelect: _view_zoomin
},
{
label: "Zoom out",
label: translate("view.zoomOut"),
keybind: [CONTROL, NUMPADMINUS],
onSelect: _view_zoomout
},
{
label: "Reset zoom",
label: translate("view.resetZoom"),
keybind: [CONTROL, NUMPADZERO],
onSelect: _view_zoomreset
},
null,
{
label: "Character Hitbox?",
label: translate("view.charHitbox"),
onSelect: _view_character_show_hitbox,
icon: Options.characterHitbox ? 1 : 0
},
{
label: "Character Camera?",
label: translate("view.charCam"),
onSelect: _view_character_show_camera,
icon: Options.characterCamera ? 1 : 0
},
{
label: "XY Axis?",
label: translate("view.axis"),
onSelect: _view_character_show_axis,
icon: Options.characterAxis ? 1 : 0
}
]
},
{
label: "Animation >",
label: translate("topBar.animation") + " >",
childs: [
{
label: "Play Animation",
label: translate("animation.playAnim"),
keybind: [SPACE],
onSelect: _animation_play,
},
{
label: "Stop Animation",
label: translate("animation.stopAnim"),
onSelect: _animation_stop
},
null,
{
label: "Change Animation ↑",
label: translate("animation.changeAnimUp"),
keybind: [W],
onSelect: _animation_up
},
{
label: "Change Animation ↓",
label: translate("animation.changeAnimDown"),
keybind: [S],
onSelect: _animation_down
},
null,
{
label: "Play Animation On Offset?",
label: translate("animation.playAnimOnOffset"),
onSelect: _animation_toggle_anim_playing_offsets,
icon: Options.playAnimOnOffset ? 1 : 0
},
@@ -433,7 +436,7 @@ class CharacterEditor extends UIState {
super.onResize(width, height);
if (!UIState.resolutionAware) return;
if (width < FlxG.initialWidth || height < FlxG.initialHeight) {
if ((width < FlxG.initialWidth || height < FlxG.initialHeight) && !Options.bypassEditorsResize) {
width = FlxG.initialWidth; height = FlxG.initialHeight;
}
@@ -450,6 +453,7 @@ class CharacterEditor extends UIState {
function _file_save(_) {
#if sys
FlxG.sound.play(Paths.sound('editors/save'));
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/data/characters/${character.curCharacter}.xml',
buildCharacter()
@@ -461,6 +465,7 @@ class CharacterEditor extends UIState {
}
function _file_saveas(_) {
FlxG.sound.play(Paths.sound('editors/save'));
openSubState(new SaveSubstate(buildCharacter(), {
defaultSaveFile: '${character.curCharacter}.xml'
}));
@@ -493,6 +498,7 @@ class CharacterEditor extends UIState {
}
function _undo(undo:CharacterEditorChange) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_UNDO_SOUND));
switch (undo) {
case null: // do nothing
case CCharEditPosition(oldPos, newPos):
@@ -563,6 +569,7 @@ class CharacterEditor extends UIState {
}
function _redo(redo:CharacterEditorChange) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND));
switch (redo) {
case null: // do nothing
case CCharEditPosition(oldPos, newPos):
@@ -682,16 +689,17 @@ class CharacterEditor extends UIState {
var stageFileList = Stage.getList(true);
if (stageFileList.length == 0) stageFileList = Stage.getList(false);
var stageText = TU.getRaw("characterEditor.stage.useStage");
for (stage in stageFileList)
newChilds.push({
label: 'Use "$stage"?',
label: stageText.format([stage]),
icon: currentStage == stage ? 1 : 0,
onSelect: (_) -> {changeStage(stage);}
});
newChilds.push(null);
newChilds.push({
label: "Use No Stage?",
label: translate("stage.useNone"),
icon: currentStage == null ? 1 : 0,
onSelect: (_) -> {changeStage(null);}
});
@@ -40,8 +40,11 @@ class CharacterInfoScreen extends UISubstateWindow {
super();
}
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("characterInfoScreen." + id, args);
public override function create() {
winTitle = 'Edit Character Properties...';
winTitle = translate("win-title");
winWidth = 824; winHeight = 400;
super.create();
@@ -50,15 +53,15 @@ class CharacterInfoScreen extends UISubstateWindow {
add(new UIText(ui.x, ui.y - 24, 0, text));
var title:UIText;
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Edit Character Info", 28));
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("title"), 28));
iconColorPicker = new UIIconColorPicker(title.x, title.y + title.height + 38, character.getIcon(), character.antialiasing, null);
add(iconColorPicker);
addLabelOn(iconColorPicker, "Icon");
addLabelOn(iconColorPicker, translate("icon"));
iconColorWheel = new UIColorwheel(iconColorPicker.x+12+125+12+22, iconColorPicker.y, character.iconColor);
add(iconColorWheel);
addLabelOn(iconColorWheel, "Icon Color");
addLabelOn(iconColorWheel, translate("iconColor"));
if (character.iconColor != null)
iconColorWheel.colorChanged = true;
@@ -66,9 +69,9 @@ class CharacterInfoScreen extends UISubstateWindow {
durationStepper = new UINumericStepper(iconColorWheel.x, iconColorWheel.y + 125 + 36, character.holdTime == -1 ? 4 : character.holdTime, 0.001, 2, 0, 9999999, 74);
add(durationStepper);
addLabelOn(durationStepper, "Sing Duration");
addLabelOn(durationStepper, translate("singDuration"));
useDurationCheckbox = new UICheckbox(durationStepper.x + durationStepper.bWidth + 20, durationStepper.y+6, "Use Sing Duration?", character.holdTime != -1);
useDurationCheckbox = new UICheckbox(durationStepper.x + durationStepper.bWidth + 20, durationStepper.y+6, translate("useSingDuration"), character.holdTime != -1);
useDurationCheckbox.onChecked = (checked:Bool) -> {durationStepper.selectable = checked;};
add(useDurationCheckbox);
@@ -86,15 +89,15 @@ class CharacterInfoScreen extends UISubstateWindow {
customPropertiesButtonList.add(new PropertyButton(prop, val, customPropertiesButtonList));
add(customPropertiesButtonList);
addLabelOn(customPropertiesButtonList, "Custom Values (Advanced)");
addLabelOn(customPropertiesButtonList, translate("customValues"));
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
saveCharacterInfo();
close();
}, 125);
add(saveButton);
closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Close", function() {
closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.close"), function() {
close();
}, 125);
add(closeButton);
@@ -1,3 +1,4 @@
package funkin.editors.character;
import funkin.backend.FlxAnimate;
@@ -26,6 +27,9 @@ class CharacterPropertiesWindow extends UISliceSprite {
public var labels:Map<UISprite, UIText> = [];
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("characterEditor.characterProperties." + id, args);
public function new(x:Float, y:Float, character:Character) @:privateAccess {
super(x, y, 424+16, 204+20, "editors/ui/inputbox");
@@ -40,7 +44,7 @@ class CharacterPropertiesWindow extends UISliceSprite {
this.changePosition(positionXStepper.value, null);
};
members.push(positionXStepper);
addLabelOn(positionXStepper, "Position (X,Y)");
addLabelOn(positionXStepper, translate("position"));
members.push(positionXYComma = new UIText(positionXStepper.x+104-32+0, positionXStepper.y + 9, 0, ",", 22));
@@ -57,17 +61,17 @@ class CharacterPropertiesWindow extends UISliceSprite {
this.changeScale(scaleStepper.value);
};
members.push(scaleStepper);
addLabelOn(scaleStepper, "Scale");
addLabelOn(scaleStepper, translate("scale"));
editCharacterButton = new UIButton(scaleStepper.x + 90 -32 + 26, scaleStepper.y-20, "Edit Info", editCharacterInfoUI, 120, 24);
editCharacterButton = new UIButton(scaleStepper.x + 90 -32 + 26, scaleStepper.y-20, translate("editInfo"), editCharacterInfoUI, 120, 24);
editCharacterButton.field.size -= 2;
members.push(editCharacterButton);
editSpriteButton = new UIButton(editCharacterButton.x, editCharacterButton.y+24+6, "Edit Sprite", editCharacterSpriteUI, 120, 24);
editSpriteButton = new UIButton(editCharacterButton.x, editCharacterButton.y+24+6, translate("editSprite"), editCharacterSpriteUI, 120, 24);
editSpriteButton.field.size -= 2;
members.push(editSpriteButton);
flipXCheckbox = new UICheckbox(scaleStepper.x+22, scaleStepper.y+32+14, "Flipped?", character.isPlayer ? !character.__baseFlipped : character.__baseFlipped);
flipXCheckbox = new UICheckbox(scaleStepper.x+22, scaleStepper.y+32+14, translate("charFlipped"), character.isPlayer ? !character.__baseFlipped : character.__baseFlipped);
flipXCheckbox.onChecked = (checked:Bool) -> {this.changeFlipX(checked);};
members.push(flipXCheckbox);
@@ -77,7 +81,7 @@ class CharacterPropertiesWindow extends UISliceSprite {
this.changeCamPosition(cameraXStepper.value, null);
};
members.push(cameraXStepper);
addLabelOn(cameraXStepper, "Camera Position (X,Y)");
addLabelOn(cameraXStepper, translate("camPosition"));
members.push(cameraXYComma = new UIText(cameraXStepper.x + 104-32+0, cameraXStepper.y+9, 0, ",", 22));
@@ -88,7 +92,7 @@ class CharacterPropertiesWindow extends UISliceSprite {
};
members.push(cameraYStepper);
antialiasingCheckbox = new UICheckbox(scaleStepper.x+22, flipXCheckbox.y+32, "Antialiased?", character.antialiasing);
antialiasingCheckbox = new UICheckbox(scaleStepper.x+22, flipXCheckbox.y+32, translate("antialiased"), character.antialiasing);
antialiasingCheckbox.onChecked = (checked:Bool) -> {this.changeAntialiasing(checked);};
members.push(antialiasingCheckbox);
@@ -97,7 +101,7 @@ class CharacterPropertiesWindow extends UISliceSprite {
CharacterEditor.instance.changeStagePosition(testAsDropDown.options[index]);
};
members.push(testAsDropDown);
addLabelOn(testAsDropDown, "Test Character As...");
addLabelOn(testAsDropDown, translate("testCharAs"));
testAsDropDown.bWidth = 193; //REFUSES TO FUCKING SET TO 170 PIECE OF SHIT!!
designedAsDropDown = new UIDropDown(testAsDropDown.x+193+22, testAsDropDown.y, 193, 32, ["BOYFRIEND", "DAD"], character.playerOffsets ? 0 : 1);
@@ -105,7 +109,7 @@ class CharacterPropertiesWindow extends UISliceSprite {
CharacterEditor.instance.changeCharacterDesginedAs(designedAsDropDown.options[index] == "BOYFRIEND");
};
members.push(designedAsDropDown);
addLabelOn(designedAsDropDown, "Char Desgined As...");
addLabelOn(designedAsDropDown, translate("charDesign"));
designedAsDropDown.bWidth = 193;
alpha = 0.7;
@@ -12,6 +12,9 @@ import funkin.backend.assets.ModsFolder;
class CharacterSelection extends EditorTreeMenu
{
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("characterSelection." + id, args);
public var modsList:Array<String> = [];
public override function create()
@@ -34,13 +37,13 @@ class CharacterSelection extends EditorTreeMenu
if(char.endsWith("/")) {
var folderName = CoolUtil.getFilename(char.substr(0, char.length-1));
list.push(new TextOption(folderName + " >", "Press ACCEPT to edit this folder.", function() {
list.push(new TextOption(folderName + " >", translate("acceptFolder"), function() {
var newModsList = Character.getList(isMods, true, char);
var newList:Array<OptionType> = generateList(newModsList, isMods, folderPath + folderName + "/");
optionsTree.add(new OptionsScreen(folderPath + folderName, "Select a character to edit in " + folderPath + folderName + "/", newList));
optionsTree.add(new OptionsScreen(folderPath + folderName, translate("desc-folder", [folderPath + folderName + "/"]), newList));
}));
} else {
list.push(new IconOption(char, "Press ACCEPT to edit this character.", Character.getIconFromCharName(folderPath + char, char),
list.push(new IconOption(char, translate("acceptCharacter"), Character.getIconFromCharName(folderPath + char, char),
function() {
FlxG.switchState(new CharacterEditor(folderPath + char));
})
@@ -48,7 +51,7 @@ class CharacterSelection extends EditorTreeMenu
}
}
list.insert(0, new NewOption("New Character", "Press ACCEPT to create a new character.", function() {
list.insert(0, new NewOption(translate("newCharacter"), translate("newCharacterDesc"), function() {
openSubState(new CharacterCreationScreen(createCharacter));
}));
@@ -57,7 +60,7 @@ class CharacterSelection extends EditorTreeMenu
var list = generateList(modsList, isMods);
main = new OptionsScreen("Character Editor", "Select a character to edit", list);
main = new OptionsScreen(TU.translate("editor.character.name"), translate("desc"), list);
DiscordUtil.call("onEditorTreeLoaded", ["Character Editor"]);
}
@@ -16,6 +16,9 @@ class CharacterSpriteScreen extends UISubstateWindow {
public var saveButton:UIButton;
public var closeButton:UIButton;
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("characterEditor.characterSpriteScreen." + id, args);
public function new(imagePath:String, ?onSave:(String, Bool)->Void) {
super();
this.imagePath = imagePath;
@@ -23,7 +26,7 @@ class CharacterSpriteScreen extends UISubstateWindow {
}
public override function create() {
winTitle = 'Edit character sprite';
winTitle = translate("win-title");
winWidth = 360; winHeight = 183;
function addLabelOn(ui:UISprite, text:String):UIText {
@@ -36,13 +39,13 @@ class CharacterSpriteScreen extends UISubstateWindow {
imageExplorer = new UIImageExplorer(20, windowSpr.y + 30 + 16 + 20, imagePath, 320, 58, (_, _) -> {onLoadImage();}, "images/characters");
add(imageExplorer);
addLabelOn(imageExplorer, "Character Image File").applyMarkup(
"Character Image File $* Required$",
addLabelOn(imageExplorer, "").applyMarkup(
translate('charImageFile'),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
ogImageSaveData = imageExplorer.getSaveData();
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, TU.translate("editor.saveClose"), function() {
addToUndo(); // should be async?? -lunar
imageExplorer.saveFiles('${Paths.getAssetsRoot()}/images/characters', () -> {
onSave('${imageExplorer.saveData.directory.length > 0 ? '${imageExplorer.saveData.directory}/' : ""}' + imageExplorer.imageName, imageExplorer.isAtlas);
@@ -53,7 +56,7 @@ class CharacterSpriteScreen extends UISubstateWindow {
saveButton.y -= saveButton.bHeight;
saveButton.selectable = false;
closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Cancel", function() {
closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.cancel"), function() {
close();
}, 125);
closeButton.color = 0xFFFF0000;
@@ -26,7 +26,7 @@ class ChartCreationScreen extends UISubstateWindow {
}
public override function create() {
winTitle = "Creating New Chart";
winTitle = TU.translate("chartCreation.title");
winWidth = 652;
winHeight = 600;
@@ -43,11 +43,11 @@ class ChartCreationScreen extends UISubstateWindow {
if (charFileList.length == 0) charFileList = Character.getList(false);
var chartTitle:UIText;
add(chartTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Chart Info", 28));
add(chartTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("chartCreation.info"), 28));
difficultyNameTextBox = new UITextBox(chartTitle.x, chartTitle.y + chartTitle.height + 36, "difficulty", 200);
add(difficultyNameTextBox);
addLabelOn(difficultyNameTextBox, "Difficulty Name");
addLabelOn(difficultyNameTextBox, TU.translate("chartCreation.diffName"));
var stageFileList = Stage.getList(true);
if (stageFileList.length == 0) stageFileList = Stage.getList(false);
@@ -55,7 +55,7 @@ class ChartCreationScreen extends UISubstateWindow {
stageTextBox = new UIAutoCompleteTextBox(difficultyNameTextBox.x + 200 + 26, difficultyNameTextBox.y, "stage", 180);
stageTextBox.suggestItems = stageFileList;
add(stageTextBox);
addLabelOn(stageTextBox, "Stage");
addLabelOn(stageTextBox, TU.translate("chartCreation.stage"));
scrollSpeedTextBox = new UINumericStepper(stageTextBox.x + 180 + 26, difficultyNameTextBox.y, 1.0, 0.1, 2, null, null, 90);
scrollSpeedTextBox.onChange = function (text:String) {
@@ -65,7 +65,7 @@ class ChartCreationScreen extends UISubstateWindow {
button.scrollSpeedStepper.value = scrollSpeedTextBox.value;
}
add(scrollSpeedTextBox);
addLabelOn(scrollSpeedTextBox, "Scroll Speed");
addLabelOn(scrollSpeedTextBox, TU.translate("chartCreation.scrollSpeed"));
strumLineList = new UIButtonList<StrumLineButton>(difficultyNameTextBox.x, difficultyNameTextBox.y+difficultyNameTextBox.bHeight+36, 620, (552-179)-16, null, FlxPoint.get(620, 246), null, 6);
strumLineList.frames = Paths.getFrames('editors/ui/inputbox');
@@ -113,20 +113,21 @@ class ChartCreationScreen extends UISubstateWindow {
}, strumLineList));
add(strumLineList);
addLabelOn(strumLineList, "Strumlines").applyMarkup(
"Strumlines $* Atleast 1 Strumline Required$",
addLabelOn(strumLineList, "").applyMarkup(
TU.translate("chartCreation.strumLineList"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
strumLineList.dragCallback = (object:StrumLineButton, oldIndex:Int, newIndex:Int) -> {object.idText.text = 'Strumline - #${newIndex}';};
var strumlineFormat = TU.getRaw("chartCreation.strumLine.format");
strumLineList.dragCallback = (object:StrumLineButton, oldIndex:Int, newIndex:Int) -> {object.idText.text = strumlineFormat.format([newIndex]);};
scrollSpeedTextBox.onChange(scrollSpeedTextBox.label.text);
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
createChart();
close();
}, 125);
add(saveButton);
closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", function() {
closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.close"), function() {
close();
}, 125);
add(closeButton);
@@ -211,12 +212,12 @@ class StrumLineButton extends UIButton {
charactersList.cameraSpacing = 0;
charactersList.addButton.callback = function()
charactersList.add(new CompactCharacterButton("New Char", subState.charFileList, charactersList));
charactersList.add(new CompactCharacterButton(TU.translate("charterStrumLine.newChar"), subState.charFileList, charactersList));
for (character in strumLine.characters)
charactersList.add(new CompactCharacterButton(character, subState.charFileList, charactersList));
members.push(charactersList);
idText = addLabelOn(charactersList, 'Strumline - #$id');
idText = addLabelOn(charactersList, TU.translate("chartCreation.strumLine.format", [id]));
cameraClipShader = new CustomShader("engine/cameraClip");
cameraClipShader.hset("clipRect", [0, 0, 100, 100]);
@@ -224,17 +225,17 @@ class StrumLineButton extends UIButton {
typeDropdown = new UIDropDown(charactersList.x + charactersList.bWidth + 16, 8+26, 200, 32, ["OPPONENT", "PLAYER", "ADDITIONAL"], strumLine.type);
members.push(typeDropdown);
addLabelOn(typeDropdown, "Type");
addLabelOn(typeDropdown, TU.translate("charterStrumLine.type"));
var stagePositionI = strumLine.position == null ? strumLine.type : ["DAD", "BOYFRIEND", "GIRLFRIEND"].indexOf(strumLine.position.toUpperCase());
stagePositionDropdown = new UIDropDown(typeDropdown.x + 200 - 32 + 26, typeDropdown.y, 200, 32, ["DAD", "BOYFRIEND", "GIRLFRIEND"], stagePositionI);
members.push(stagePositionDropdown);
addLabelOn(stagePositionDropdown, "Stage Position");
addLabelOn(stagePositionDropdown, TU.translate("charterStrumLine.stagePos"));
hudScaleStepper = new UINumericStepper(typeDropdown.x, typeDropdown.y + 64, strumLine.strumScale == null ? 1 : strumLine.strumScale, 0.001, 2, null, null, 74);
members.push(hudScaleStepper);
addLabelOn(hudScaleStepper, "Scale");
addLabelOn(hudScaleStepper, TU.translate("charterStrumLine.scale"));
var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.type == 1 ? 0.75 : 0.25) : strumLine.strumLinePos;
@@ -244,22 +245,22 @@ class StrumLineButton extends UIButton {
hudXStepper = new UINumericStepper(hudScaleStepper.x + 80 - 32 + 26, hudScaleStepper.y, startingPos.x, 0.01, 2, 0, 2, 84);
members.push(hudXStepper);
addLabelOn(hudXStepper, "Hud Position (X [Ratio 0-1],Y)");
addLabelOn(hudXStepper, TU.translate("charterStrumLine.hudPos"));
members.push(XYComma = new UIText(hudXStepper.x + 84 - 32 + 0, hudXStepper.y + 9, 0, ",", 22));
hudYStepper = new UINumericStepper(hudXStepper.x + 84 - 32 + 26, hudXStepper.y, startingPos.y, 0.001, 2, null, null, 84);
members.push(hudYStepper);
visibleCheckbox = new UICheckbox(hudYStepper.x + hudYStepper.bWidth + 42, hudYStepper.y + 9, "Visible?", strumLine.visible == null ? true : strumLine.visible);
visibleCheckbox = new UICheckbox(hudYStepper.x + hudYStepper.bWidth + 42, hudYStepper.y + 9, TU.translate("charterStrumLine.visible"), strumLine.visible == null ? true : strumLine.visible);
members.push(visibleCheckbox);
scrollSpeedStepper = new UINumericStepper(typeDropdown.x, typeDropdown.y + 128, strumLine.scrollSpeed, 0.1, 2, 0, 10, 82);
scrollSpeedStepper.selectable = strumLine.scrollSpeed != null;
members.push(scrollSpeedStepper);
addLabelOn(scrollSpeedStepper, "Scroll Speed");
addLabelOn(scrollSpeedStepper, TU.translate("charterStrumLine.scrollSpeed"));
usesChartScrollSpeed = new UICheckbox(scrollSpeedStepper.x + 104, typeDropdown.y + 135, "Uses charts scroll speed?", strumLine.scrollSpeed == null);
usesChartScrollSpeed = new UICheckbox(scrollSpeedStepper.x + 104, typeDropdown.y + 135, TU.translate("charterStrumLine.useChartScrollSpeed"), strumLine.scrollSpeed == null);
usesChartScrollSpeed.onChecked = function(b) {
if(b) {
scrollSpeedStepper.value = subState.scrollSpeedTextBox.value;
@@ -318,7 +319,8 @@ class CompactCharacterButton extends UIButton {
autoAlpha = false;
charIcon = new HealthIcon(funkin.game.Character.getIconFromCharName(char));
charIcon.scale.set(0.2, 0.2);
var size = Std.int(150 * 0.2);
charIcon.setUnstretchedGraphicSize(size, size, true);
charIcon.updateHitbox();
charIcon.setPosition(10, bHeight/2 - charIcon.height / 2);
charIcon.scrollFactor.set(1,1);
@@ -330,11 +332,10 @@ class CompactCharacterButton extends UIButton {
textBox.antialiasing = true;
textBox.onChange = function(char:String) {
char = funkin.game.Character.getIconFromCharName(char);
var image = Paths.image("icons/" + char);
if(!Assets.exists(image))
image = Paths.image("icons/" + Flags.DEFAULT_HEALTH_ICON);
charIcon.loadGraphic(image, true, 150, 150);
charIcon.setIcon(char);
charIcon.setUnstretchedGraphicSize(size, size, true);
charIcon.updateHitbox();
charIcon.setPosition(10, bHeight/2 - charIcon.height / 2);
}
deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), null, function () {
@@ -29,7 +29,7 @@ class ChartDataScreen extends UISubstateWindow {
Charter.instance.vocals.pause();
for (strumLine in Charter.instance.strumLines.members) strumLine.vocals.pause();
winTitle = 'Chart Properties';
winTitle = TU.translate("chartDataScreen.title");
winWidth = 420; winHeight = 230; // guys look, the funny numbers!
super.create();
@@ -38,7 +38,7 @@ class ChartDataScreen extends UISubstateWindow {
add(new UIText(ui.x, ui.y - 24, 0, text));
var title:UIText;
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Edit Chart Data", 28));
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("chartDataScreen.editChartData"), 28));
var stageFileList = Stage.getList(true);
if (stageFileList.length == 0) stageFileList = Stage.getList(false);
@@ -46,19 +46,19 @@ class ChartDataScreen extends UISubstateWindow {
stageTextBox = new UIAutoCompleteTextBox(title.x, title.y + title.height + 38, PlayState.SONG.stage, 200);
stageTextBox.suggestItems = stageFileList;
add(stageTextBox);
addLabelOn(stageTextBox, "Stage");
addLabelOn(stageTextBox, TU.translate("chartDataScreen.stage"));
scrollSpeedStepper = new UINumericStepper(stageTextBox.x + 200 + 26, stageTextBox.y, data.scrollSpeed, 0.1, 2, 0, 10, 82);
add(scrollSpeedStepper);
addLabelOn(scrollSpeedStepper, "Scroll Speed");
addLabelOn(scrollSpeedStepper, TU.translate("chartDataScreen.scrollSpeed"));
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
saveInfo();
close();
}, 125);
add(saveButton);
closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() {
closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() {
close();
}, 125);
add(closeButton);
File diff suppressed because it is too large Load Diff
@@ -26,7 +26,7 @@ class CharterAutoSaveUI extends UISliceSprite {
icon.animation.play("icon"); icon.animation.curAnim.curFrame = 0;
members.push(icon);
members.push(autosavingText = new UIText(x+12+10+4, y+8, 0, "Autosaving in 10 seconds...", 12));
members.push(autosavingText = new UIText(x+12+10+4, y+8, 0, TU.translate("editor.autosavingIn", ["?", "..."]), 12));
progressBarBack = new FlxSprite(x + 10, y + bHeight - 20).makeGraphic(Std.int(bWidth-20), 10, 0x00000000, true);
progressBarBack.drawRoundRect(0, 0, progressBarBack.width, progressBarBack.height, 4, 6, 0xFF727272, null, {smoothing: false});
@@ -45,7 +45,8 @@ class CharterAutoSaveUI extends UISliceSprite {
icon.animation.curAnim.curFrame = 2;
(new FlxTimer()).start(1, (_) -> {disappearAnimation(true);});
autosavingText.text += " (Canceled)!"; cancelButton.visible = false;
autosavingText.text += " " + TU.translate("editor.autosavingCanceledSuffix");
cancelButton.visible = false;
for (member in [this, autosavingText]) member.color = 0xFFE67F7F;
for (member in [progressBar, progressBarBack]) member.color = 0xFFE67F7F;
}, 24, 14);
@@ -68,8 +69,10 @@ class CharterAutoSaveUI extends UISliceSprite {
__tween = FlxTween.num(0, 1, time, null, (v:Float) -> {
if ((progress = v) < .95) {
autosavingText.text = 'Autosaving in ${Math.min(Math.round(time), 1+Math.floor(Math.abs(time-(progress*time))))} seconds';
autosavingText.text += [for (i in 0...(Math.floor((progress*time*3)%4))) "."].join("");
autosavingText.text = TU.translate("editor.autosavingIn", [
Math.min(Math.round(time), 1+Math.floor(Math.abs(time-(progress*time)))),
".".repeat(Math.floor((progress*time*3)%4))
]);
}
});
__timer = new FlxTimer();
@@ -79,6 +82,8 @@ class CharterAutoSaveUI extends UISliceSprite {
icon.animation.curAnim.curFrame = 1;
for (member in [this, progressBar, progressBarBack, autosavingText]) member.color = 0xFFA3EC95;
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_AUTOSAVE_SOUND));
(new FlxTimer()).start(1, (_) -> {disappearAnimation();});
});
}
@@ -105,11 +110,11 @@ class CharterAutoSaveUI extends UISliceSprite {
bar.follow(this, 10, bHeight-20);
bar.alpha = alpha;
}
progressBar.clipRect = progressBar.clipRect.set(0, 0, progressBar.width*progress,progressBar.height);
progressBar.rawClipRect = progressBar.rawClipRect.set(0, 0, progressBar.frameWidth*progress,progressBar.frameHeight);
}
public function appearAnimation() {
autosavingText.text = "Autosaving in seconds..."; progress = 0; icon.animation.curAnim.curFrame = 0;
progress = 0; icon.animation.curAnim.curFrame = 0;
for (member in [this, autosavingText, progressBar, progressBarBack]) member.color = 0xFFFFFFFF;
x = -(320); alpha=0; FlxTween.cancelTweensOf(this); cancelled = false; cancelButton.visible = true;
@@ -43,8 +43,6 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> {
public var conductorSprY:Float = 0;
public var bottomLimitY:Float = 0;
public var sectionsVisible:Bool = true;
public var beatsVisible:Bool = true;
// Just here so you can update display sprites all dat and above
public var strumlinesAmount:Int = 0;
@@ -80,8 +78,6 @@ class CharterBackdropGroup extends FlxTypedGroup<CharterBackdrop> {
grid.conductorFollowerSpr.y = conductorSprY;
grid.bottomSeparator.y = (grid.bottomLimit.y = bottomLimitY)-2;
grid.sectionSeparator.visible = sectionsVisible;
grid.beatSeparator.visible = beatsVisible;
grid.waveformSprite.shader = strumLine.waveformShader;
@@ -174,8 +170,7 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
public var waveformSprite:FlxSprite;
public var conductorFollowerSpr:FlxSprite;
public var beatSeparator:FlxBackdrop;
public var sectionSeparator:FlxBackdrop;
public var beatSeparator:CharterGridSeperator;
public var notesGroup:NotesDrawGroup = new NotesDrawGroup();
public var strumLine:CharterStrumline;
@@ -197,23 +192,14 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
waveformSprite.updateHitbox();
add(waveformSprite);
sectionSeparator = new FlxBackdrop(null, Y, 0, 0);
sectionSeparator.y = -2;
sectionSeparator.visible = Options.charterShowSections;
beatSeparator = new CharterGridSeperator();
beatSeparator.makeSolid(1, 1, -1);
beatSeparator.alpha = 0.5;
beatSeparator.scrollFactor.set(1, 1);
beatSeparator.scale.set((4 * 40), 2);
beatSeparator.updateHitbox();
beatSeparator = new FlxBackdrop(null, Y, 0, 0);
beatSeparator.y = -1;
beatSeparator.visible = Options.charterShowBeats;
for(sep in [sectionSeparator, beatSeparator]) {
sep.makeSolid(1, 1, -1);
sep.alpha = 0.5;
sep.scrollFactor.set(1, 1);
sep.scale.set((4 * 40), sep == sectionSeparator ? 4 : 2);
sep.updateHitbox();
}
add(beatSeparator);
add(sectionSeparator);
add(notesGroup);
bottomSeparator = new FlxSprite(0,-2);
@@ -263,7 +249,7 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
keyCount = strumLine.keyCount;
} else alpha = 0.9;
for (spr in [gridBackDrop, sectionSeparator, beatSeparator, topLimit, bottomLimit,
for (spr in [gridBackDrop, beatSeparator, topLimit, bottomLimit,
topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite]) {
spr.x = x; if (spr != waveformSprite) spr.alpha = alpha;
spr.cameras = this.cameras;
@@ -274,9 +260,6 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
if (__lastKeyCount != keyCount) gridShader.hset("segments", keyCount);
__lastKeyCount = keyCount;
sectionSeparator.spacing.y = (10 * Conductor.beatsPerMeasure * Conductor.stepsPerBeat) - 1;
beatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1;
topLimit.scale.set(keyCount * 40, Math.ceil(FlxG.height / cameras[0].zoom));
topLimit.updateHitbox();
topLimit.y = -topLimit.height;
@@ -284,7 +267,7 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
bottomLimit.scale.set(keyCount * 40, Math.ceil(FlxG.height / cameras[0].zoom));
bottomLimit.updateHitbox();
for (spr in [conductorFollowerSpr, sectionSeparator, beatSeparator, topSeparator, bottomSeparator]) {
for (spr in [conductorFollowerSpr, beatSeparator, topSeparator, bottomSeparator]) {
spr.scale.x = keyCount * 40;
spr.updateHitbox();
}
@@ -310,9 +293,161 @@ class CharterBackdrop extends FlxTypedGroup<FlxBasic> {
}
}
class CharterGridSeperatorBase extends FlxSprite {
private static var minStep:Float = 0;
private static var maxStep:Float = 0;
private static var minBeat:Float = 0;
private static var maxBeat:Float = 0;
private static var minMeasure:Float = 0;
private static var maxMeasure:Float = 0;
private static var lastMinBeat:Float = -1;
private static var lastMaxBeat:Float = -1;
private static var lastMinMeasure:Float = -1;
private static var lastMaxMeasure:Float = -1;
public static var lastConductorSprY:Float = Math.NEGATIVE_INFINITY;
private static var beatStepTimes:Array<Float> = [];
private static var measureStepTimes:Array<Float> = [];
private static var timeSignatureChangeGaps:Array<Float> = [];
private function recalculateBeats() {
var conductorSprY = Charter.instance.gridBackdrops.conductorSprY;
if (conductorSprY == lastConductorSprY) return;
var zoomOffset = ((FlxG.height * (1/cameras[0].zoom)) * 0.5);
minStep = (conductorSprY - zoomOffset)/40;
maxStep = (conductorSprY + zoomOffset)/40;
var minTime:Float = Conductor.getStepsInTime(minStep);
var maxTime:Float = Conductor.getStepsInTime(maxStep);
var minBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(minTime)];
var maxBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(maxTime)];
minBeat = Conductor.getTimeInBeats(minTime);
maxBeat = Conductor.getTimeInBeats(maxTime);
minMeasure = minBpmChange.measureTime + (minBeat - minBpmChange.beatTime) / minBpmChange.beatsPerMeasure;
maxMeasure = maxBpmChange.measureTime + (maxBeat - maxBpmChange.beatTime) / maxBpmChange.beatsPerMeasure;
//cap out the beats/measures at the end of the song
var endTime = Conductor.getStepsInTime(Charter.instance.__endStep);
var endBeat = Conductor.getTimeInBeats(endTime);
var endBpmChange = Conductor.bpmChangeMap[Conductor.getTimeInChangeIndex(endTime)];
var endMeasure = endBpmChange.measureTime + (endBeat - endBpmChange.beatTime) / endBpmChange.beatsPerMeasure;
if (maxBeat > endBeat) maxBeat = endBeat;
if (maxMeasure > endMeasure) maxMeasure = endMeasure;
if (minMeasure < 0) minMeasure = 0;
if (minBeat < 0) minBeat = 0;
//only calculate if needed
if ((minBeat != lastMinBeat) || (maxBeat != lastMaxBeat) || (minMeasure != lastMinMeasure) || (maxMeasure != lastMaxMeasure) || lastConductorSprY == Math.NEGATIVE_INFINITY) {
calculateTimeSignatureGaps();
calculateStepTimes();
lastMinBeat = minBeat;
lastMaxBeat = maxBeat;
lastMinMeasure = minMeasure;
lastMaxMeasure = maxMeasure;
}
lastConductorSprY = conductorSprY;
}
private inline function calculateTimeSignatureGaps() {
//for time signatures that start mid step
timeSignatureChangeGaps.splice(0, timeSignatureChangeGaps.length);
for (i => change in Conductor.bpmChangeMap) {
if (change.stepTime >= minStep && change.stepTime <= maxStep) {
//get step while ignoring the current change
var index:Int = CoolUtil.boundInt(i-1, 0, Conductor.bpmChangeMap.length - 1);
var step:Float = Conductor.getTimeWithBPMInSteps(change.songTime, index, Conductor.getTimeWithIndexInBPM(change.songTime, index));
if (Math.ceil(step) - step > 0 && (step - Math.floor(step)) > FlxMath.EPSILON) { //mid step change
timeSignatureChangeGaps.push(step);
}
}
}
}
private inline function calculateStepTimes() {
beatStepTimes.splice(0, beatStepTimes.length);
for (i in Math.floor(minBeat)...Math.ceil(maxBeat)) {
beatStepTimes.push(Conductor.getTimeInSteps(Conductor.getBeatsInTime(i)));
}
measureStepTimes.splice(0, measureStepTimes.length);
for (i in Math.floor(minMeasure)...Math.ceil(maxMeasure)) {
measureStepTimes.push(Conductor.getTimeInSteps(Conductor.getMeasuresInTime(i)));
}
}
override public function draw() {
//should only need to recalculate once per frame and will be shared across each instance
recalculateBeats();
drawTimeSignatureChangeGaps();
if (Options.charterShowBeats) drawBeats();
if (Options.charterShowSections) drawMeasures();
}
private function drawBeats(offset:Float = 0.0) {
for (i in beatStepTimes) {
y = (i*40)+offset;
super.draw();
}
}
private function drawMeasures(offset:Float = 0.0) {
for (i in measureStepTimes) {
y = (i*40)+offset;
super.draw();
}
}
private function drawTimeSignatureChangeGaps() {
if (timeSignatureChangeGaps.length == 0) return;
var prevColor = color;
var prevBlend = blend;
color = 0xFF888888;
blend = MULTIPLY;
for (step in timeSignatureChangeGaps) {
y = step*40;
var diff = Math.ceil(step) - step;
scale.y = diff*40;
updateHitbox();
super.draw();
}
color = prevColor;
blend = prevBlend;
}
}
class CharterGridSeperator extends CharterGridSeperatorBase {
override private function drawBeats(offset:Float = 0.0) {
scale.y = 2;
updateHitbox();
super.drawBeats(-2);
}
override private function drawMeasures(offset:Float = 0.0) {
scale.y = 4;
updateHitbox();
super.drawMeasures(-3);
}
}
class EventBackdrop extends FlxBackdrop {
public var eventBeatSeparator:FlxBackdrop;
public var eventSecSeparator:FlxBackdrop;
public var eventBeatSeparator:CharterEventGridSeperator;
public var topSeparator:FlxSprite;
public var bottomSeparator:FlxSprite;
@@ -325,25 +460,11 @@ class EventBackdrop extends FlxBackdrop {
alpha = 0.9;
// Separators
eventSecSeparator = new FlxBackdrop(null, Y, 0, 0);
eventSecSeparator.y = -2;
eventSecSeparator.visible = Options.charterShowSections;
eventBeatSeparator = new FlxBackdrop(null, Y, 0, 0);
eventBeatSeparator.y = -1;
eventBeatSeparator.visible = Options.charterShowBeats;
for(sep in [eventSecSeparator, eventBeatSeparator]) {
sep.makeSolid(1, 1, -1);
sep.alpha = 0.5;
sep.scrollFactor.set(1, 1);
}
eventSecSeparator.scale.set(20, 4);
eventSecSeparator.updateHitbox();
eventBeatSeparator.scale.set(10, 2);
eventBeatSeparator.updateHitbox();
eventBeatSeparator = new CharterEventGridSeperator();
eventBeatSeparator.makeSolid(1, 1, -1);
eventBeatSeparator.alpha = 0.5;
eventBeatSeparator.scrollFactor.set(1, 1);
eventBeatSeparator.global = global;
bottomSeparator = new FlxSprite(0,-2);
bottomSeparator.makeSolid(1, 1, -1);
@@ -364,23 +485,34 @@ class EventBackdrop extends FlxBackdrop {
public override function draw() {
super.draw();
eventSecSeparator.spacing.y = (10 * Conductor.beatsPerMeasure * Conductor.stepsPerBeat) - 1;
eventBeatSeparator.spacing.y = (20 * Conductor.stepsPerBeat) - 1;
eventSecSeparator.cameras = cameras;
eventSecSeparator.x = this.global ? x : ((x+width) - 20);
if (eventSecSeparator.visible) eventSecSeparator.draw();
eventBeatSeparator.cameras = cameras;
eventBeatSeparator.x = this.global ? x : ((x+width) - 10);
if (eventBeatSeparator.visible) eventBeatSeparator.draw();
eventBeatSeparator.xPos = global ? x : x+width;
eventBeatSeparator.draw();
topSeparator.x = this.global ? x : ((x+width) - 20);
topSeparator.x = global ? x : (x+width) - 20;
topSeparator.cameras = this.cameras;
if (!eventSecSeparator.visible) topSeparator.draw();
if (!Options.charterShowSections) topSeparator.draw();
bottomSeparator.x = this.global ? x : ((x+width) - 20);
bottomSeparator.x = global ? x : (x+width) - 20;
bottomSeparator.cameras = this.cameras;
bottomSeparator.draw();
}
}
class CharterEventGridSeperator extends CharterGridSeperatorBase {
public var xPos:Float = 0.0;
public var global:Bool = false;
override private function drawBeats(offset:Float = 0.0) {
scale.set(10, 2);
updateHitbox();
x = global ? xPos : xPos-10;
super.drawBeats(-2);
}
override private function drawMeasures(offset:Float = 0.0) {
scale.set(20, 4);
updateHitbox();
x = global ? xPos : xPos-20;
super.drawMeasures(-3);
}
override private function drawTimeSignatureChangeGaps() {}
}
@@ -0,0 +1,65 @@
package funkin.editors.charter;
import flixel.util.FlxColor;
import flixel.math.FlxPoint;
import funkin.backend.chart.ChartData.ChartBookmark;
class CharterBookmarkCreation extends UISubstateWindow {
public var saveButton:UIButton;
public var closeButton:UIButton;
public var textBox:UITextBox;
public var colorPicker:UICompactColorwheel;
var callback:Bool->String->FlxColor->Float->Void;
var bookmarkStep:Float = 0;
override public function new(step:Float, close:Bool->String->FlxColor->Float->Void)
{
super();
winWidth = 320;
winHeight = 250;
winTitle = TU.translate("charter.bookmarks.createBookmarkTitle");
callback = close;
bookmarkStep = step;
}
public override function create() {
FlxG.sound.music.pause();
Charter.instance.vocals.pause();
function addLabelOn(ui:UISprite, text:String):UIText {
var text:UIText = new UIText(ui.x, ui.y - 24, 0, text);
ui.members.push(text);
return text;
}
super.create();
textBox = new UITextBox(windowSpr.x + 20, windowSpr.y + 70, TU.translate("charter.bookmarks.newBookmarkName"), 276);
textBox.antialiasing = true;
add(textBox);
addLabelOn(textBox, TU.translate("charter.bookmarks.createBookmarkName"));
colorPicker = new UICompactColorwheel(textBox.x, textBox.y + textBox.bHeight + 34, flixel.util.FlxColor.RED);
add(colorPicker);
addLabelOn(colorPicker, TU.translate("charter.bookmarks.createBookmarkColor"));
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
callback(true, textBox.label.text, colorPicker.curColor, bookmarkStep);
close();
}, 125);
add(saveButton);
closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() {
callback(false, "", FlxColor.TRANSPARENT, -1);
close();
}, 125);
add(closeButton);
closeButton.color = 0xFFFF0000;
closeButton.x -= closeButton.bWidth;
}
}
@@ -0,0 +1,95 @@
package funkin.editors.charter;
import flixel.math.FlxPoint;
import funkin.backend.chart.ChartData.ChartBookmark;
class CharterBookmarkList extends UISubstateWindow {
public var bookmarkList:UIButtonList<BookmarkButton>;
public var saveButton:UIButton;
public var closeButton:UIButton;
public override function create() {
FlxG.sound.music.pause();
Charter.instance.vocals.pause();
winTitle = TU.translate("charter.bookmarks.editBookmarkListTitle");
winWidth = 380;
winHeight = 390;
super.create();
var title:UIText;
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("charter.bookmarks.editBookmarksTitle"), 28));
bookmarkList = new UIButtonList<BookmarkButton>(20, title.y + title.height + 10, winWidth - 40, 342 - 85 - 16, null, FlxPoint.get(winWidth - 40, (342 - 85 - 16)/4), null, 0);
bookmarkList.cameraSpacing = 0;
for (b in Charter.instance.getBookmarkList())
bookmarkList.add(new BookmarkButton(0, 0, b, bookmarkList));
add(bookmarkList);
bookmarkList.frames = Paths.getFrames('editors/ui/inputbox');
bookmarkList.content.remove(bookmarkList.addButton); //i dont need it
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
saveList();
close();
}, 125);
add(saveButton);
closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() {
close();
}, 125);
add(closeButton);
closeButton.color = 0xFFFF0000;
closeButton.x -= closeButton.bWidth;
}
public function saveList() {
var oldList:Array<ChartBookmark> = Charter.instance.getBookmarkList();
var newList:Array<ChartBookmark> = [for (btn in bookmarkList.buttons.members) {time: btn.bookmark.time, name: btn.textBox.label.text, color: btn.bookmark.color}];
PlayState.SONG.bookmarks = newList;
Charter.instance.updateBookmarks();
Charter.undos.addToUndo(CEditBookmarks(oldList, newList));
}
}
class BookmarkButton extends UIButton {
public var textBox:UITextBox;
public var deleteButton:UIButton;
public var deleteIcon:FlxSprite;
public var bookmark:ChartBookmark;
public function new(x:Float, y:Float, bookmark:ChartBookmark, parent:UIButtonList<BookmarkButton>) {
super(x, y, '', null, Std.int(parent.buttonSize.x), Std.int(parent.buttonSize.y));
this.bookmark = bookmark;
autoAlpha = false;
members.push(textBox = new UITextBox(10, 10, bookmark.name, 276));
textBox.antialiasing = true;
deleteButton = new UIButton(textBox.x + textBox.label.width + 10, textBox.y, null, function () {
parent.remove(this);
}, 32);
deleteButton.color = 0xFFFF0000;
deleteButton.autoAlpha = false;
members.push(deleteButton);
deleteIcon = new FlxSprite(deleteButton.x + (15/2), deleteButton.y + 8).loadGraphic(Paths.image('editors/delete-button'));
deleteIcon.antialiasing = false;
members.push(deleteIcon);
}
override function update(elapsed:Float) {
textBox.y = (y + bHeight/2) - (textBox.bHeight/2);
deleteButton.x = textBox.x + textBox.bWidth + 14; deleteButton.y = textBox.y;
deleteIcon.x = deleteButton.x + (15/2); deleteIcon.y = deleteButton.y + 8;
deleteButton.selectable = selectable;
deleteButton.shouldPress = shouldPress;
super.update(elapsed);
}
}
+381 -35
View File
@@ -1,9 +1,14 @@
package funkin.editors.charter;
import openfl.display.BitmapData;
import openfl.geom.ColorTransform;
import flixel.group.FlxSpriteGroup;
import flixel.math.FlxPoint;
import flixel.system.FlxAssets.FlxGraphicAsset;
import funkin.backend.chart.ChartData.ChartEvent;
import funkin.backend.scripting.DummyScript;
import funkin.backend.scripting.Script;
import funkin.backend.system.Conductor;
import funkin.editors.charter.Charter.ICharterSelectable;
import funkin.editors.charter.CharterBackdropGroup.EventBackdrop;
import funkin.game.Character;
@@ -58,8 +63,8 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
selectedColorTransform(colorTransform);
useColorTransform = true;
for (sprite in icons) {
@:privateAccess sprite.colorTransform.__identity();
for (sprite in icons) @:privateAccess {
sprite.colorTransform.__identity();
selectedColorTransform(sprite.colorTransform);
}
@@ -76,25 +81,84 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
transform.blueOffset += selected ? 168 : 0;
}
/**
* Pack data is a list of 4 strings separated by `________PACKSEP________`
* [0] Event Name
* [1] Event Script
* [2] Event JSON Info
* [3] Event Icon
* [4] Event UI Script / Icon Script
**/
@:dox(hide) public static function getPackData(name:String):Array<String> {
var packFile = Paths.pack('events/${name}');
if (Assets.exists(packFile)) {
return Assets.getText(packFile).split('________PACKSEP________');
}
return null;
}
@:dox(hide) public static function getUIScript(event:ChartEvent, caller:String):Script {
var uiScript = Paths.script('data/events/${event.name}.ui');
var script:Script = null;
if(Assets.exists(uiScript)) {
script = Script.create(uiScript);
} else {
var packData = getPackData(event.name);
if(packData != null) {
var scriptFile = packData[4];
if(scriptFile != null) {
script = Script.fromString(scriptFile, uiScript);
}
}
}
if(script != null && !(script is DummyScript)) {
// classes and functions
script.set("EventIconGroup", EventIconGroup); // automatically imported
script.set("EventNumber", EventNumber); // automatically imported
script.set("getIconFromStrumline", getIconFromStrumline);
script.set("getIconFromCharName", getIconFromCharName);
script.set("generateDefaultIcon", generateDefaultIcon);
script.set("generateEventIconDurationArrow", generateEventIconDurationArrow);
script.set("generateEventIconNumbers", generateEventIconNumbers);
script.set("generateEventIconWarning", generateEventIconWarning);
script.set("getPackData", getPackData);
script.set("getEventComponent", getEventComponent);
// data
script.set("event", event);
script.set("caller", caller);
script.load();
}
return script;
}
/**
* Generates the default event icon for the wanted event
* @param name The name of the event
* @return The icon
**/
private static function generateDefaultIcon(name:String) {
var isBase64:Bool = false;
var path:String = Paths.image('editors/charter/event-icons/$name');
var defaultPath = Paths.image('editors/charter/event-icons/Unknown');
if (!Assets.exists(path)) path = defaultPath;
if(!Assets.exists(path)) {
path = defaultPath;
var packPath = Paths.pack('events/$name');
if (Assets.exists(packPath)) {
var packText = Assets.getText(packPath).split('________PACKSEP________');
var packImg = packText[3];
if(packImg != null && packImg.length > 0) {
isBase64 = !packImg.startsWith("assets/");
path = packImg;
var packData = getPackData(name);
if(packData != null) {
var packImg = packData[3];
if(packImg != null && packImg.length > 0) {
isBase64 = !packImg.startsWith("assets/");
path = packImg;
}
}
}
path = path.trim();
var graphic:FlxGraphicAsset = try {
isBase64 ? openfl.display.BitmapData.fromBase64(path, 'UTF8') : path;
isBase64 ? BitmapData.fromBase64(path, 'UTF8') : path;
} catch(e:Dynamic) {
Logs.trace('Failed to load event icon: ${e.toString()}', ERROR);
isBase64 = false;
@@ -106,27 +170,176 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
graphic = defaultPath;
}
return new FlxSprite().loadGraphic(graphic);
return new FlxSprite(graphic);
}
public static function generateEventIcon(event:ChartEvent) {
return switch(event.name) {
default:
generateDefaultIcon(event.name);
case "Camera Movement":
// custom icon for camera movement
var state:Charter = cast FlxG.state;
if (event.params != null && event.params[0] != null && event.params[0] >= 0 && event.params[0] < state.strumLines.length) {
// camera movement, use health icon
var icon = Character.getIconFromCharName(state.strumLines.members[event.params[0]].strumLine.characters[0]);
var healthIcon = new HealthIcon(icon);
healthIcon.setUnstretchedGraphicSize(32, 32, false);
healthIcon.scrollFactor.set(1, 1);
healthIcon.active = false;
healthIcon;
} else
generateDefaultIcon(event.name);
/**
* Gets a component sprite from the editors/charter/event-icons/components folder
* If you wanna use a number, please use the EventNumber class instead
* @param type The type of component to get
* @param x The x position of the sprite (optional)
* @param y The y position of the sprite (optional)
* @return The component sprite
**/
public static function getEventComponent(type:String, x:Float = 0.0, y:Float = 0.0) {
var componentPath = Paths.image("editors/charter/event-icons/components/" + type);
if (Assets.exists(componentPath)) return new FlxSprite(x, y, componentPath);
Logs.trace('Could not find component $type', WARNING);
return null;
}
/**
* Expected to be called from inside of a ui script,
* calling this elsewhere might cause unexpected results or crashes
**/
public static function getIconFromStrumline(index:Null<Int>) {
var state = cast(FlxG.state, Charter);
if (index != null && index >= 0 && index < state.strumLines.length) {
return getIconFromCharName(state.strumLines.members[index].strumLine.characters[0]);
}
return null;
}
public static function getIconFromCharName(name:String) {
var icon = Character.getIconFromCharName(name);
var healthIcon = new HealthIcon(icon);
CoolUtil.setUnstretchedGraphicSize(healthIcon, 32, 32, false);
healthIcon.scrollFactor.set(1, 1);
healthIcon.active = false;
return healthIcon;
}
public static function generateEventIcon(event:ChartEvent, inMenu:Bool = true):FlxSprite {
var script = getUIScript(event, "event-icon");
if(script != null && !(script is DummyScript)) {
script.set("inMenu", inMenu);
if(script.get("generateIcon") != null) {
var res:FlxSprite = script.call("generateIcon");
if(res != null)
return res;
}
}
switch(event.name) {
case "Time Signature Change":
if(event.params != null && (event.params[0] >= 0 || event.params[1] >= 0)) {
var group = new EventIconGroup();
group.add(generateDefaultIcon(event.name));
group.add({ // top
var num = new EventNumber(9, -1, event.params[0], EventNumber.ALIGN_CENTER);
num.active = false;
num;
});
group.add({ // bottom
var num = new EventNumber(9, 10, event.params[1], EventNumber.ALIGN_CENTER);
num.active = false;
num;
});
if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group);
return group;
}
case "Continuous BPM Change":
if(event.params != null && event.params[1] != null) {
var group = new EventIconGroup();
group.add(generateDefaultIcon("BPM Change Start"));
if (!inMenu) {
generateEventIconDurationArrow(group, event.params[1]);
group.members[0].y -= 2;
generateEventIconNumbers(group, event.params[0], 3);
}
if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group);
return group;
} else {
return generateDefaultIcon("BPM Change Start");
}
case "BPM Change":
if(event.params != null && event.params[0] != null) {
var group = new EventIconGroup();
group.add(generateDefaultIcon(event.name));
if (!inMenu) {
group.members[0].y -= 2;
generateEventIconNumbers(group, event.params[0], 3);
}
if (Conductor.invalidEvents.contains(event)) generateEventIconWarning(group);
return group;
}
case "Scroll Speed Change":
if(event.params != null && !inMenu) {
var group = new EventIconGroup();
group.add(generateDefaultIcon(event.name));
if (event.params[0]) generateEventIconDurationArrow(group, event.params[2]);
group.members[0].y -= 2;
generateEventIconNumbers(group, event.params[1]);
return group;
}
case "Camera Movement":
// camera movement, use health icon
if(event.params != null) {
var icon = getIconFromStrumline(event.params[0]);
if(icon != null) return icon;
}
}
return generateDefaultIcon(event.name);
}
private static function generateEventIconNumbers(group:EventIconGroup, number:Float, x:Float = 4, y:Float = 15, spacing:Float = 5, precision:Int = 3) {
group.add({
var num = new EventNumber(x, y, number, EventNumber.ALIGN_CENTER, spacing, precision);
if (num.numWidth > 20) {
num.scale.x = num.scale.y = 20 / num.numWidth;
}
num.active = false;
num;
});
}
private static function generateEventIconDurationArrow(group:EventIconGroup, stepDuration:Float) {
//var group = new EventIconGroup();
//group.add(generateDefaultIcon(startIcon));
var xOffset = 4;
var yGap = 24;
var endGap = 2;
if (stepDuration >= 0.55) { //min time for showing arrow
var tail = new FlxSprite(xOffset, yGap);
var arrow = new FlxSprite(xOffset, (stepDuration * 40) + endGap);
var arrowSegment = new FlxSprite(xOffset, yGap);
tail.frames = arrow.frames = arrowSegment.frames = Paths.getSparrowAtlas("editors/charter/event-icons/components/arrow-down");
group.add({
tail.animation.addByPrefix("tail", "tail");
tail.animation.play("tail");
tail;
});
group.add({
arrowSegment.animation.addByPrefix("segment", "segment");
arrowSegment.animation.play("segment");
arrowSegment.scale.y = endGap + (stepDuration * 40) - (tail.height + yGap);
arrowSegment.updateHitbox();
arrowSegment.y += tail.height;
arrowSegment;
});
group.add({
arrow.animation.addByPrefix("arrow", "arrow");
arrow.animation.play("arrow");
arrow;
});
}
}
private static function generateEventIconWarning(group:EventIconGroup) {
for (spr in group) {
spr.colorTransform.redMultiplier = spr.colorTransform.greenMultiplier = spr.colorTransform.blueMultiplier = 0.5;
spr.colorTransform.redOffset = 100;
}
group.add(getEventComponent("warning", 16, -8));
group.copyColorTransformToChildren = false;
}
public override function onHovered() {
@@ -154,19 +367,152 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable {
}
for(event in events) {
var spr = generateEventIcon(event);
var spr = generateEventIcon(event, false);
icons.push(spr);
members.push(spr);
}
draggable = true;
for (event in events)
if (event.name == "BPM Change") {
draggable = false;
break;
}
bWidth = 37 + (icons.length * 22);
x = (snappedToGrid && eventsBackdrop != null && global ? eventsBackdrop.x - bWidth : (global ? 0 : -bWidth));
}
}
class EventIconGroup extends FlxSpriteGroup {
public var forceWidth:Float = 16;
public var forceHeight:Float = 16;
public var dontTransformChildren:Bool = false;
public var copyColorTransformToChildren:Bool = true;
public function new() {
super();
colorTransform = new ColorTransform();
scrollFactor.set(1, 1);
}
override function preAdd(sprite:FlxSprite):Void
{
super.preAdd(sprite);
sprite.scrollFactor.set(1, 1);
}
public override function transformChildren<V>(Function:FlxSprite->V->Void, Value:V):Void
{
if (dontTransformChildren)
return;
super.transformChildren(Function, Value);
}
override function set_x(Value:Float):Float
{
if (exists && x != Value)
transformChildren(xTransform, Value - x); // offset
return x = Value;
}
override function set_y(Value:Float):Float
{
if (exists && y != Value)
transformChildren(yTransform, Value - y); // offset
return y = Value;
}
override function get_width() {
return forceWidth;
}
override function get_height() {
return forceHeight;
}
override public function draw() {
@:privateAccess
if (copyColorTransformToChildren && colorTransform != null) for (child in members) child.colorTransform.__copyFrom(colorTransform);
super.draw();
}
override function update(elapsed:Float) {
super.update(elapsed);
}
}
class EventNumber extends FlxSprite {
public static inline final ALIGN_NORMAL:Int = 0;
public static inline final ALIGN_CENTER:Int = 1;
public var digits:Array<Int> = [];
public static inline final FRAME_POINT:Int = 10;
public static inline final FRAME_NEGATIVE:Int = 11;
public var align:Int = ALIGN_NORMAL;
public var spacing:Float = 6;
public function new(x:Float, y:Float, number:Float, ?align:Int, spacing:Float = 6, precision:Int = 3) {
super(x, y);
this.digits = [];
this.align = align == null ? ALIGN_NORMAL : align;
this.spacing = spacing;
if (number == 0) {
this.digits.insert(0, 0);
}
else {
var decimals:Float = FlxMath.roundDecimal(Math.abs(number % 1), precision);
if (decimals > 0) this.digits.insert(0, FRAME_POINT);
while(decimals > 0) {
this.digits.push(Math.floor(decimals * 10));
decimals = FlxMath.roundDecimal((decimals * 10) % 1, precision);
}
var ints = Std.int(Math.abs(number));
if (ints == 0) this.digits.insert(0, 0);
while (ints > 0) {
this.digits.insert(0, ints % 10);
ints = Std.int(ints / 10);
}
if (number < 0) {
this.digits.insert(0, FRAME_NEGATIVE);
}
}
loadGraphic(Paths.image('editors/charter/event-icons/components/eventNums'), true, 6, 7);
}
override function update(elapsed:Float) {
super.update(elapsed);
}
override function draw() {
var baseX = x;
var offsetX = 0.0;
if(align == ALIGN_CENTER) offsetX = -(digits.length - 1) * spacing * Math.abs(scale.x) / 2;
x = baseX + offsetX;
for (i in 0...digits.length) {
frame = frames.frames[digits[i]];
super.draw();
x += spacing * Math.abs(scale.x);
}
x = baseX;
}
public var numWidth(get, never):Float;
private function get_numWidth():Float {
return Math.abs(scale.x) * spacing * digits.length;
}
public var numHeight(get, never):Float;
private function get_numHeight():Float {
return Math.abs(scale.y) * frameHeight;
}
public override function updateHitbox():Void {
var numWidth = this.numWidth;
var numHeight = this.numHeight;
width = numWidth;
height = numHeight;
offset.set(-0.5 * (numWidth - spacing * digits.length), -0.5 * (numHeight - frameHeight));
centerOrigin();
}
}
@@ -15,7 +15,7 @@ class CharterEventAdd extends UISliceSprite {
this.global = flipX = global;
sideText = new UIText(0, -40, 0, global ? "Global Event" : "Local Event", 12);
sideText = new UIText(0, -40, 0, TU.translate("charter.eventType-" + (global ? "global" : "local")), 12);
sideText.alignment = "center"; sideText.alpha = 0.75;
text = new UIText(0, 0, 0, "");
@@ -29,15 +29,27 @@ class CharterEventAdd extends UISliceSprite {
if (FlxG.mouse.justReleased && FlxG.state.subState == null) {
if (curCharterEvent != null)
Charter.instance.openSubState(new CharterEventScreenNew(curCharterEvent));
else
Charter.instance.openSubState(new CharterEventScreen(step, global));
else {
CharterEventGroup.stopThisFuckingShitDudeIstg = true;
var chartEvent = new CharterEvent(step, [], global);
chartEvent.global = global;
Charter.instance.createSelection([chartEvent]);
Charter.instance.openSubState(new CharterEventScreenNew(chartEvent));
sprAlpha = 0;
alpha = sprAlpha * 0.75;
text.alpha = sprAlpha;
sideText.alpha = sprAlpha;
}
}
}
public override function update(elapsed:Float) {
super.update(elapsed);
text.follow(this, global ? bWidth - text.width - (text.text == "Add event" ? 15 : 20) : 20, (bHeight - text.height) / 2);
if (FlxG.state.subState != null) return;
text.follow(this, global ? bWidth - text.width - (text.text == TU.translate("charter.addEvent") ? 15 : 20) : 20, (bHeight - text.height) / 2);
sideText.follow(this, (bWidth/2) - (sideText.fieldWidth/2), -(sideText.height + 2));
alpha = sprAlpha * 0.75;
text.alpha = sprAlpha;
@@ -46,18 +58,20 @@ class CharterEventAdd extends UISliceSprite {
}
public function updatePos(step:Float) {
if (FlxG.state.subState != null) return;
curCharterEvent = null;
this.step = step;
this.y = (step * 40) - (bHeight / 2);
text.text = "Add event";
text.text = TU.translate("charter.addEvent");
framesOffset = 0; bWidth = 37 + Math.ceil(text.width);
x = global ? Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].x + (40*Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].keyCount) : -(bWidth);
}
public function updateEdit(event:CharterEvent) {
if (FlxG.state.subState != null) return;
curCharterEvent = event;
this.y = event.y;
text.text = "Edit";
text.text = TU.translate("charter.editEvent");
framesOffset = 9; bWidth = 27 + Math.ceil(text.width) + event.bWidth;
x = global ? Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].x + (40*Charter.instance.strumLines.members[Charter.instance.strumLines.members.length-1].keyCount) : -(bWidth);
}
@@ -4,6 +4,7 @@ import flixel.util.FlxSort;
import funkin.editors.charter.CharterBackdropGroup.EventBackdrop;
class CharterEventGroup extends FlxTypedGroup<CharterEvent> {
public static var stopThisFuckingShitDudeIstg = false;
public var eventsBackdrop:EventBackdrop;
public var eventsRowText:UIText;
@@ -11,12 +12,12 @@ class CharterEventGroup extends FlxTypedGroup<CharterEvent> {
var __lastSort:Int = 0;
public override function update(elapsed:Float) {
filterEvents();
super.update(elapsed);
if (!CharterEventGroup.stopThisFuckingShitDudeIstg) filterEvents();
if (autoSort && members.length != __lastSort)
sortEvents();
eventsRowText.y = FlxMath.lerp(eventsRowText.y, -40 + (members[0] != null ? Math.min(members[0].y, 0) : 0), 1/20);
super.update(elapsed);
}
public override function remove(v:CharterEvent, force:Bool = true):CharterEvent {
@@ -38,7 +38,7 @@ class CharterEventScreen extends UISubstateWindow {
var creatingEvent:Bool = chartEvent == null;
if (creatingEvent) chartEvent = new CharterEvent(step, [], global);
winTitle = creatingEvent ? "Create Event Group" : "Edit Event Group";
winTitle = TU.translate("charterEventScreen.title" + (creatingEvent ? "-creating" : "-editing"));
winWidth = 960;
super.create();
@@ -59,7 +59,7 @@ class CharterEventScreen extends UISubstateWindow {
});
eventsList.add(new EventButton(events[events.length-1], CharterEvent.generateEventIcon(events[events.length-1]), events.length-1, this, eventsList));
changeTab(events.length-1);
}));
}, chartEvent.step));
for (k=>i in events)
eventsList.add(new EventButton(i, CharterEvent.generateEventIcon(i), k, this, eventsList));
add(eventsList);
@@ -67,10 +67,12 @@ class CharterEventScreen extends UISubstateWindow {
paramsPanel = new FlxGroup();
add(paramsPanel);
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
saveCurTab();
chartEvent.refreshEventIcons();
Charter.instance.updateBPMEvents();
if (events.length <= 0 && !creatingEvent)
Charter.instance.deleteSelection([chartEvent]);
else if (events.length > 0) {
@@ -78,12 +80,11 @@ class CharterEventScreen extends UISubstateWindow {
chartEvent.events = [for (i in eventsList.buttons.members) i.event];
chartEvent.global = global;
chartEvent.refreshEventIcons();
if (creatingEvent && events.length > 0)
Charter.instance.createSelection([chartEvent]);
else {
chartEvent.refreshEventIcons();
Charter.instance.updateBPMEvents();
Charter.undos.addToUndo(CEditEvent(chartEvent, oldEvents, [for (event in events) Reflect.copy(event)]));
}
}
@@ -93,7 +94,7 @@ class CharterEventScreen extends UISubstateWindow {
saveButton.x -= saveButton.bWidth;
add(saveButton);
closeButton = new UIButton(saveButton.x - 10, saveButton.y, "Close", ()->close());
closeButton = new UIButton(saveButton.x - 10, saveButton.y, TU.translate("editor.close"), ()->close());
closeButton.color = 0xFFFF0000;
closeButton.x -= closeButton.bWidth;
add(closeButton);
@@ -120,12 +121,20 @@ class CharterEventScreen extends UISubstateWindow {
if (id >= 0 && id < events.length) {
curEvent = id;
var curEvent = events[curEvent];
eventName.text = curEvent.name;
var visualName = curEvent.name;
var tuId = "charter.events." + TU.raw2Id(curEvent.name);
if(TU.exists(tuId))
visualName = TU.translate(tuId);
eventName.text = visualName;
// add new elements
var y:Float = eventName.y + eventName.height + 10;
for(k=>param in EventsData.getEventParams(curEvent.name)) {
function addLabel() {
var label:UIText = new UIText(eventName.x, y, 0, param.name);
var visualName = param.name;
// TODO: add translations
var label:UIText = new UIText(eventName.x, y, 0, visualName);
y += label.height + 4;
paramsPanel.add(label);
};
@@ -153,7 +162,11 @@ class CharterEventScreen extends UISubstateWindow {
numericStepper;
case TStrumLine:
addLabel();
var dropdown = new UIDropDown(eventName.x, y, 320, 32, [for(k=>s in cast(FlxG.state, Charter).strumLines.members) 'Strumline #${k+1} (${s.strumLine.characters[0]})'], cast value);
var strumlineFormat = TU.getRaw("charterEventScreen.strumLine.format");
var dropdown = new UIDropDown(eventName.x, y, 320, 32, [
for(k=>s in cast(FlxG.state, Charter).strumLines.members)
strumlineFormat.format([k+1, s.strumLine.characters[0]])
], cast value);
paramsPanel.add(dropdown); paramsFields.push(dropdown);
dropdown;
case TColorWheel:
@@ -194,7 +207,7 @@ class CharterEventScreen extends UISubstateWindow {
y += cast(lastAdded, FlxSprite).height + 6;
}
} else {
eventName.text = "No event";
eventName.text = TU.translate("charterEventScreen.noEvent"); // Maybe should be No Events?
curEvent = -1;
}
}
@@ -202,11 +215,11 @@ class CharterEventScreen extends UISubstateWindow {
public function saveCurTab() {
if (curEvent < 0) return;
events[curEvent].params = [
for(p in paramsFields) {
var dataParams = EventsData.getEventParams(events[curEvent].name);
var params:Array<Dynamic> = [
for (i => p in paramsFields) {
if (p is UIDropDown) {
var dataParams = EventsData.getEventParams(events[curEvent].name);
if (dataParams[paramsFields.indexOf(p)].type == TStrumLine) cast(p, UIDropDown).index;
if (dataParams[i].type == TStrumLine) cast(p, UIDropDown).index;
else cast(p, UIDropDown).label.text;
}
else if (p is UINumericStepper) {
@@ -227,6 +240,14 @@ class CharterEventScreen extends UISubstateWindow {
null;
}
];
while(dataParams.length > 0 && {
var index = params.length - 1;
var dataParam = dataParams[index];
dataParam.saveIfDefault == false && params[index] == dataParam.defValue;
}) params.pop();
events[curEvent].params = params;
}
}
@@ -88,7 +88,7 @@ class CharterEventScreenNew extends MusicBeatSubstate {
});
eventsList.add(new EventButtonNew(events[events.length-1], CharterEvent.generateEventIcon(events[events.length-1]), events.length-1, this, eventsList));
changeTab(events.length-1);
}));
}, chartEvent.step));
};
for (k=>i in events)
eventsList.add(new EventButtonNew(i, CharterEvent.generateEventIcon(i), k, this, eventsList));
@@ -284,19 +284,19 @@ class CharterEventScreenNew extends MusicBeatSubstate {
}
public function quit() {
CharterEventGroup.stopThisFuckingShitDudeIstg = false;
saveCurTab();
chartEvent.refreshEventIcons();
if (events.length <= 0)
Charter.instance.deleteSelection([chartEvent]);
else if (events.length > 0) {
var oldEvents:Array<ChartEvent> = chartEvent.events.copy();
chartEvent.events = [for (i in eventsList.buttons.members) i.event];
chartEvent.refreshEventIcons();
Charter.instance.updateBPMEvents();
Charter.undos.addToUndo(CEditEvent(chartEvent, oldEvents, [for (event in events) Reflect.copy(event)]));
}
chartEvent.refreshEventIcons();
Charter.instance.updateBPMEvents();
close();
}
@@ -1,9 +1,11 @@
package funkin.editors.charter;
import funkin.backend.system.Conductor;
import funkin.backend.chart.EventsData;
class CharterEventTypeSelection extends UISubstateWindow {
var callback:String->Void;
var eventStepTime:Float;
var buttons:Array<UIButton> = [];
@@ -13,13 +15,14 @@ class CharterEventTypeSelection extends UISubstateWindow {
var upIndicator:UIText;
var downIndicator:UIText;
public function new(callback:String->Void) {
public function new(callback:String->Void, eventStepTime:Float) {
super();
this.callback = callback;
this.eventStepTime = eventStepTime;
}
public override function create() {
winTitle = "Choose an event type...";
winTitle = TU.translate("charterEventTypeSelection.title");
super.create();
var w:Int = winWidth - 20;
@@ -32,8 +35,21 @@ class CharterEventTypeSelection extends UISubstateWindow {
buttonsBG.frames = Paths.getFrames('editors/ui/inputbox');
add(buttonsBG);
var disableConductorEvents:Bool = false;
var disableOnlyContinuousChanges:Bool = false;
for (change in Conductor.bpmChangeMap) {
if (change.continuous && MathUtil.greaterThanEqual(eventStepTime, change.stepTime) && MathUtil.lessThan(eventStepTime, change.endStepTime)) {
disableOnlyContinuousChanges = MathUtil.equal(eventStepTime, change.stepTime); //allow time sig and instant bpm changes on the same event
disableConductorEvents = !disableOnlyContinuousChanges;
}
}
for(k=>eventName in EventsData.eventsList) {
var button = new UIButton(0, (32 * k), eventName, function() {
var visualName = eventName;
var tuId = "charter.events." + TU.raw2Id(eventName);
if(TU.exists(tuId))
visualName = TU.translate(tuId);
var button = new UIButton(0, (32 * k), visualName, function() {
close();
callback(eventName);
}, w);
@@ -52,11 +68,16 @@ class CharterEventTypeSelection extends UISubstateWindow {
icon.x = button.x + 8;
icon.y = button.y + Math.abs(button.bHeight - icon.height) / 2;
add(icon);
if (disableConductorEvents && (eventName == "Time Signature Change" || eventName == "Continuous BPM Change" || eventName == "BPM Change") || (disableOnlyContinuousChanges && eventName == "Continuous BPM Change")) {
button.selectable = button.shouldPress = false;
button.autoAlpha = true;
}
}
windowSpr.bHeight = 61 + (32 * (17));
add(new UIButton(10, windowSpr.bHeight-42, "Cancel", function() {
add(new UIButton(10, windowSpr.bHeight-42, TU.translate("editor.cancel"), function() {
close();
}, w));
@@ -79,7 +100,7 @@ class CharterEventTypeSelection extends UISubstateWindow {
sinner += elapsed;
for (button in buttons)
button.selectable = buttonsBG.hovered;
if (button.shouldPress) button.selectable = buttonsBG.hovered;
buttonCameras.zoom = subCam.zoom;
@@ -3,6 +3,7 @@ package funkin.editors.charter;
import flixel.math.FlxPoint;
import funkin.backend.chart.ChartData.ChartMetaData;
import funkin.editors.extra.PropertyButton;
import funkin.game.HealthIcon;
using StringTools;
@@ -14,12 +15,12 @@ class CharterMetaDataScreen extends UISubstateWindow {
public var songNameTextBox:UITextBox;
public var bpmStepper:UINumericStepper;
public var beatsPerMeasureStepper:UINumericStepper;
public var stepsPerBeatStepper :UINumericStepper;
public var denominatorStepper:UINumericStepper;
public var customPropertiesButtonList:UIButtonList<PropertyButton>;
public var displayNameTextBox:UITextBox;
public var iconTextBox:UITextBox;
public var iconSprite:FlxSprite;
public var iconSprite:HealthIcon;
public var opponentModeCheckbox:UICheckbox;
public var coopAllowedCheckbox:UICheckbox;
public var colorWheel:UIColorwheel;
@@ -30,8 +31,11 @@ class CharterMetaDataScreen extends UISubstateWindow {
this.metadata = metadata;
}
inline function translate(id:String):String
return TU.translate("charterMetaDataScreen." + id);
public override function create() {
winTitle = "Edit Metadata";
winTitle = translate("title");
winWidth = 1056;
winHeight = 520;
@@ -45,54 +49,54 @@ class CharterMetaDataScreen extends UISubstateWindow {
add(new UIText(ui.x, ui.y - 24, 0, text));
var title:UIText;
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Song Data", 28));
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("songData"), 28));
songNameTextBox = new UITextBox(title.x, title.y + title.height + 36, metadata.name);
add(songNameTextBox);
addLabelOn(songNameTextBox, "Song Name");
addLabelOn(songNameTextBox, translate("songName"));
bpmStepper = new UINumericStepper(songNameTextBox.x + 320 + 26, songNameTextBox.y, metadata.bpm, 1, 2, 1, null, 90);
add(bpmStepper);
addLabelOn(bpmStepper, "BPM");
addLabelOn(bpmStepper, translate("bpm"));
beatsPerMeasureStepper = new UINumericStepper(bpmStepper.x + 60 + 26, bpmStepper.y, metadata.beatsPerMeasure, 1, 0, 1, null, 54);
add(beatsPerMeasureStepper);
addLabelOn(beatsPerMeasureStepper, "Time Signature");
addLabelOn(beatsPerMeasureStepper, translate("timeSignature"));
add(new UIText(beatsPerMeasureStepper.x + 30, beatsPerMeasureStepper.y + 3, 0, "/", 22));
stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, metadata.stepsPerBeat, 1, 0, 1, null, 54);
add(stepsPerBeatStepper);
denominatorStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, Math.floor(16 / metadata.stepsPerBeat), 1, 0, 1, null, 54);
add(denominatorStepper);
add(title = new UIText(songNameTextBox.x, songNameTextBox.y + 10 + 46, 0, "Menus Data (Freeplay/Story)", 28));
add(title = new UIText(songNameTextBox.x, songNameTextBox.y + 10 + 46, 0, translate("menusData"), 28));
displayNameTextBox = new UITextBox(title.x, title.y + title.height + 36, metadata.displayName);
add(displayNameTextBox);
addLabelOn(displayNameTextBox, "Display Name");
addLabelOn(displayNameTextBox, translate("displayName"));
iconTextBox = new UITextBox(displayNameTextBox.x + 320 + 26, displayNameTextBox.y, metadata.icon, 150);
iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);}
add(iconTextBox);
addLabelOn(iconTextBox, "Icon");
addLabelOn(iconTextBox, translate("icon"));
updateIcon(metadata.icon);
opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, "Opponent Mode", metadata.opponentModeAllowed);
opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, translate("opponentMode"), metadata.opponentModeAllowed);
add(opponentModeCheckbox);
addLabelOn(opponentModeCheckbox, "Modes Allowed");
addLabelOn(opponentModeCheckbox, translate("modesAllowed"));
coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, "Co-op Mode", metadata.coopAllowed);
coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, translate("coopAllowed"), metadata.coopAllowed);
add(coopAllowedCheckbox);
colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, metadata.parsedColor);
colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, metadata.color);
add(colorWheel);
addLabelOn(colorWheel, "Color");
addLabelOn(colorWheel, translate("color"));
difficultiesTextBox = new UITextBox(opponentModeCheckbox.x, opponentModeCheckbox.y + 6 + 32 + 26, metadata.difficulties.join(", "));
add(difficultiesTextBox);
addLabelOn(difficultiesTextBox, "Difficulties");
addLabelOn(difficultiesTextBox, translate("difficulties"));
customPropertiesButtonList = new UIButtonList<PropertyButton>(stepsPerBeatStepper.x + 80 + 26 + 105, songNameTextBox.y, 290, 316, '', FlxPoint.get(280, 35), null, 5);
customPropertiesButtonList = new UIButtonList<PropertyButton>(denominatorStepper.x + 80 + 26 + 105, songNameTextBox.y, 290, 316, '', FlxPoint.get(280, 35), null, 5);
customPropertiesButtonList.frames = Paths.getFrames('editors/ui/inputbox');
customPropertiesButtonList.cameraSpacing = 0;
customPropertiesButtonList.addButton.callback = function() {
@@ -101,19 +105,19 @@ class CharterMetaDataScreen extends UISubstateWindow {
for (val in Reflect.fields(metadata.customValues))
customPropertiesButtonList.add(new PropertyButton(val, Reflect.field(metadata.customValues, val), customPropertiesButtonList));
add(customPropertiesButtonList);
addLabelOn(customPropertiesButtonList, "Custom Values (Advanced)");
addLabelOn(customPropertiesButtonList, translate("customValues"));
for (checkbox in [opponentModeCheckbox, coopAllowedCheckbox])
{checkbox.y += 6; checkbox.x += 4;}
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, TU.translate("editor.saveClose"), function() {
saveMeta();
close();
}, 125);
saveButton.x -= saveButton.bWidth;
saveButton.y -= saveButton.bHeight;
closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() {
closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() {
close();
}, 125);
closeButton.color = 0xFFFF0000;
@@ -124,22 +128,14 @@ class CharterMetaDataScreen extends UISubstateWindow {
}
function updateIcon(icon:String) {
if (iconSprite == null) add(iconSprite = new FlxSprite());
if (iconSprite == null) add(iconSprite = new HealthIcon());
if (iconSprite.animation.exists(icon)) return;
@:privateAccess iconSprite.animation.clearAnimations();
var path:String = Paths.image('icons/$icon');
if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON);
iconSprite.loadGraphic(path, true, 150, 150);
iconSprite.animation.add(icon, [0], 0, false);
iconSprite.antialiasing = true;
iconSprite.animation.play(icon);
iconSprite.scale.set(0.5, 0.5);
iconSprite.setIcon(icon);
var size = Std.int(150 * 0.5);
iconSprite.setUnstretchedGraphicSize(size, size, true);
iconSprite.updateHitbox();
iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2));
iconSprite.scrollFactor.set(1, 1);
}
public function saveMeta() {
@@ -154,11 +150,10 @@ class CharterMetaDataScreen extends UISubstateWindow {
name: songNameTextBox.label.text,
bpm: bpmStepper.value,
beatsPerMeasure: Std.int(beatsPerMeasureStepper.value),
stepsPerBeat: Std.int(stepsPerBeatStepper.value),
stepsPerBeat: Std.int(16 / denominatorStepper.value),
displayName: displayNameTextBox.label.text,
icon: iconTextBox.label.text,
color: colorWheel.curColorString,
parsedColor: colorWheel.curColor,
color: colorWheel.curColor,
opponentModeAllowed: opponentModeCheckbox.checked,
coopAllowed: coopAllowedCheckbox.checked,
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()],
@@ -15,32 +15,33 @@ class CharterNoteTypesList extends UISubstateWindow {
Charter.instance.vocals.pause();
for (strumLine in Charter.instance.strumLines.members) strumLine.vocals.pause();
winTitle = 'Note Types List Editor';
winTitle = TU.translate("charterNoteTypesList.title");
winWidth = 380; winHeight = 390;
super.create();
var title:UIText;
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Edit Note Types", 28));
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("charterNoteTypesList.editNoteTypes"), 28));
var noteTypesFileList = getNoteTypesList(true);
if (noteTypesFileList.length == 0) noteTypesFileList = getNoteTypesList(false);
noteTypesList = new UIButtonList<NoteTypeButton>(20, title.y + title.height + 10, winWidth - 40, 342 - 85 - 16, null, FlxPoint.get(winWidth - 40, (342 - 85 - 16)/4), null, 0);
noteTypesList.addButton.callback = () -> noteTypesList.add(new NoteTypeButton(0, 0, 'Note Type ${noteTypesList.buttons.members.length}', noteTypesList.buttons.members.length, noteTypesFileList, noteTypesList));
noteTypesList.addButton.callback = () -> noteTypesList.add(new NoteTypeButton(0, 0, TU.translate("charterNoteTypesList.editType", [noteTypesList.buttons.members.length]), noteTypesList.buttons.members.length, noteTypesFileList, noteTypesList));
noteTypesList.cameraSpacing = 0; noteTypesList.dragCallback = (object:NoteTypeButton, oldIndex:Int, newIndex:Int) -> {object.IDText.text = '${newIndex}.';};
for (i=>noteType in Charter.instance.noteTypes)
// TODO: make this translatable
noteTypesList.add(new NoteTypeButton(0, 0, noteType, i, noteTypesFileList, noteTypesList));
add(noteTypesList);
noteTypesList.frames = Paths.getFrames('editors/ui/inputbox');
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
saveList();
close();
}, 125);
add(saveButton);
closeButton = new UIButton(saveButton.x - 20, saveButton.y, "Close", function() {
closeButton = new UIButton(saveButton.x - 20, saveButton.y, TU.translate("editor.close"), function() {
close();
}, 125);
add(closeButton);
@@ -1,5 +1,6 @@
package funkin.editors.charter;
import funkin.backend.chart.Chart;
import funkin.backend.chart.ChartData.ChartMetaData;
import funkin.backend.chart.ChartData;
import funkin.backend.system.framerate.Framerate;
@@ -26,26 +27,32 @@ class CharterSelection extends EditorTreeMenu {
freeplayList = FreeplaySonglist.get(false);
var list:Array<OptionType> = [
for(s in freeplayList.songs) new EditorIconOption(s.name, "Press ACCEPT to choose a difficulty to edit.", s.icon, function() {
for(s in freeplayList.songs) new EditorIconOption(s.name, TU.translate("charterSelection.acceptSong"), s.icon, function() {
curSong = s;
var list:Array<OptionType> = [
for(d in s.difficulties) if (d != "")
new TextOption(d, "Press ACCEPT to edit the chart for the selected difficulty", function() {
new TextOption(d, TU.translate("charterSelection.acceptDifficulty"), function() {
FlxG.switchState(new Charter(s.name, d));
})
];
list.push(new NewOption("New Difficulty", "Press ACCEPT to create a new difficulty.", function() {
#if sys
var newDiff = TU.translate("charterSelection.newDifficulty");
list.push(new NewOption(newDiff, newDiff, function() {
FlxG.state.openSubState(new ChartCreationScreen(saveChart));
}));
optionsTree.add(new OptionsScreen(s.name, "Select a difficulty to continue.", list));
}, s.parsedColor.getDefault(0xFFFFFFFF))
#end
optionsTree.add(new OptionsScreen(s.name, TU.translate("charterSelection.selectDifficulty"), list));
}, s.color.getDefault(0xFFFFFFFF))
];
list.insert(0, new NewOption("New Song", "Press ACCEPT to create a new song.", function() {
#if sys
var newSong = TU.translate("charterSelection.newSong");
list.insert(0, new NewOption(newSong, newSong, function() {
FlxG.state.openSubState(new SongCreationScreen(saveSong));
}));
#end
main = new OptionsScreen("Chart Editor", "Select a song to modify the charts from.", list);
main = new OptionsScreen(TU.translate("editor.chart.name"), TU.translate("charterSelection.desc"), list);
DiscordUtil.call("onEditorTreeLoaded", ["Chart Editor"]);
}
@@ -88,12 +95,13 @@ class CharterSelection extends EditorTreeMenu {
}
}
public function saveSong(creation:SongCreationData) {
#if sys
public function saveSong(creation:SongCreationData, ?callback:String -> SongCreationData -> Void) {
var songAlreadyExists:Bool = [for (s in freeplayList.songs) s.name.toLowerCase()].contains(creation.meta.name.toLowerCase());
if (songAlreadyExists) {
openSubState(new UIWarningSubstate("Creating Song: Error!", "The song you are trying to create already exists, if you would like to override it delete the song first!", [
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.song-exists-title"), TU.translate("chartCreation.warnings.song-exists-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}},
]));
return;
}
@@ -109,24 +117,31 @@ class CharterSelection extends EditorTreeMenu {
sys.FileSystem.createDirectory('$songFolder/charts');
// Save Files
CoolUtil.safeSaveFile('$songFolder/meta.json', Json.stringify(creation.meta, Flags.JSON_PRETTY_PRINT));
CoolUtil.safeSaveFile('$songFolder/meta.json', Chart.makeMetaSaveable(creation.meta));
if (creation.instBytes != null) sys.io.File.saveBytes('$songFolder/song/Inst.${Flags.SOUND_EXT}', creation.instBytes);
if (creation.voicesBytes != null) sys.io.File.saveBytes('$songFolder/song/Voices.${Flags.SOUND_EXT}', creation.voicesBytes);
if (creation.playerVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Player.${Flags.SOUND_EXT}', creation.playerVocals);
if (creation.oppVocals != null) sys.io.File.saveBytes('$songFolder/song/Voices-Opponent.${Flags.SOUND_EXT}', creation.oppVocals);
#end
var option = new EditorIconOption(creation.meta.name, "Press ACCEPT to choose a difficulty to edit.", creation.meta.icon, function() {
if (callback != null)
callback(songFolder, creation);
var option = new EditorIconOption(creation.meta.name, TU.translate("charterSelection.acceptSong"), creation.meta.icon, function() {
curSong = creation.meta;
var list:Array<OptionType> = [
for(d in creation.meta.difficulties)
if (d != "") new TextOption(d, "Press ACCEPT to edit the chart for the selected difficulty", function() {
if (d != "") new TextOption(d, TU.translate("charterSelection.acceptDifficulty"), function() {
FlxG.switchState(new Charter(creation.meta.name, d));
})
];
list.push(new NewOption("New Difficulty", "Press ACCEPT to create a new difficulty.", function() {
var newDiff = TU.translate("charterSelection.newDifficulty");
list.push(new NewOption(newDiff, newDiff, function() {
FlxG.state.openSubState(new ChartCreationScreen(saveChart));
}));
optionsTree.insert(1, new OptionsScreen(creation.meta.name, "Select a difficulty to continue.", list));
}, creation.meta.parsedColor.getDefault(0xFFFFFFFF));
optionsTree.insert(1, new OptionsScreen(creation.meta.name, TU.translate("charterSelection.selectDifficulty"), list));
}, creation.meta.color.getDefault(0xFFFFFFFF));
// Add to List
freeplayList.songs.insert(0, creation.meta);
@@ -137,8 +152,8 @@ class CharterSelection extends EditorTreeMenu {
var difficultyAlreadyExists:Bool = curSong.difficulties.contains(name);
if (difficultyAlreadyExists) {
openSubState(new UIWarningSubstate("Creating Chart: Error!", "The chart you are trying to create already exists, if you would like to override it delete the chart first!", [
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
openSubState(new UIWarningSubstate(TU.translate("chartCreation.warnings.chart-exists-title"), TU.translate("chartCreation.warnings.chart-exists-body"), [
{label: TU.translate("editor.ok"), color: 0xFFFF0000, onClick: function(t) {}},
]));
return;
}
@@ -150,8 +165,9 @@ class CharterSelection extends EditorTreeMenu {
CoolUtil.safeSaveFile('$songFolder/charts/${name}.json', Json.stringify(data, Flags.JSON_PRETTY_PRINT));
// Add to List
// duplicated code, todo: fix this
curSong.difficulties.push(name);
var option = new TextOption(name, "Press ACCEPT to edit the chart for the selected difficulty", function() {
var option = new TextOption(name, TU.translate("charterSelection.acceptDifficulty"), function() {
FlxG.switchState(new Charter(curSong.name, name));
});
optionsTree.members[optionsTree.members.length-1].insert(optionsTree.members[optionsTree.members.length-1].length-1, option);
@@ -160,7 +176,8 @@ class CharterSelection extends EditorTreeMenu {
var meta = Json.parse(sys.io.File.getContent('$songFolder/meta.json'));
if (meta.difficulties != null && !meta.difficulties.contains(name)) {
meta.difficulties.push(name);
CoolUtil.safeSaveFile('$songFolder/meta.json', Json.stringify(meta));
CoolUtil.safeSaveFile('$songFolder/meta.json', Chart.makeMetaSaveable(meta));
}
}
#end
}
@@ -88,8 +88,7 @@ class CharterStrumline extends UISprite {
button = new CharterStrumlineOptions(this);
members.push(button);
vocals = strumLine.vocalsSuffix.length > 0 ? FlxG.sound.load(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : new FlxSound();
vocals.group = FlxG.sound.defaultMusicGroup;
updateInfo();
selectedWaveform = -1;
}
@@ -136,8 +135,16 @@ class CharterStrumline extends UISprite {
healthIcons.add(healthIcon);
}
vocals = null;
vocals = strumLine.vocalsSuffix.length > 0 ? FlxG.sound.load(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : new FlxSound();
var asset = strumLine.vocalsSuffix.length > 0 ? Assets.getSound(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : null;
if (vocals == null) FlxG.sound.list.add(vocals = new FlxSound());
if (asset != null) {
vocals.reset();
vocals.loadEmbedded(asset);
}
else {
vocals.destroy();
}
vocals.group = FlxG.sound.defaultMusicGroup;
}
}
@@ -145,7 +152,8 @@ class CharterStrumline extends UISprite {
class CharterStrumlineOptions extends UITopMenuButton {
var strLine:CharterStrumline;
public function new(parent:CharterStrumline) {
super(0, 95, null, "Options ↓", []);
// TODO: better id for this
super(0, 95, null, TU.translate("charter.strumLine.button-name"), []);
strLine = parent;
}
@@ -159,14 +167,14 @@ class CharterStrumlineOptions extends UITopMenuButton {
public override function openContextMenu() {
contextMenu = [
{
label: "Hitsounds",
label: TU.translate("charter.strumLine.hitsounds"),
onSelect: function(_) {
strLine.hitsounds = !strLine.hitsounds;
},
icon: strLine.hitsounds ? 1 : 0
},
{
label: "Mute Vocals",
label: TU.translate("charter.strumLine.muteVocals"),
onSelect: function(_) {
strLine.vocals.volume = strLine.vocals.volume > 0 ? 0 : 1;
},
@@ -174,7 +182,7 @@ class CharterStrumlineOptions extends UITopMenuButton {
},
null,
{
label: "Edit",
label: TU.translate("charter.strumLine.edit"),
onSelect: function (_) {
Charter.instance.editStrumline(strLine.strumLine);
},
@@ -182,7 +190,7 @@ class CharterStrumlineOptions extends UITopMenuButton {
icon: 4
},
{
label: "Delete",
label: TU.translate("charter.strumLine.delete"),
onSelect: function (_) {
Charter.instance.deleteStrumlineFromData(strLine.strumLine);
},
@@ -192,7 +200,7 @@ class CharterStrumlineOptions extends UITopMenuButton {
];
contextMenu.insert(0, {
label: "No Waveform",
label: TU.translate("charter.strumLine.noWaveform"),
onSelect: function(_) {strLine.selectedWaveform = -1;},
icon: strLine.selectedWaveform == -1 ? 1 : 0
});
@@ -59,7 +59,7 @@ class CharterStrumlineButton extends UISprite {
super.update(elapsed);
button.updateSpriteRect();
if(UIState.state.curContextMenu == null && UIState.state.isOverlapping(button, button.__rect)) {
if (UIState.state.curContextMenu == null && UIState.state.isOverlapping(button, button.__rect) && button.visible) {
buttonScale.set(0.95, 0.95);
if (FlxG.mouse.justPressed && onClick != null) {
onClick();
@@ -56,7 +56,7 @@ class CharterStrumlineScreen extends UISubstateWindow {
keyCount: 4
};
winTitle = creatingStrumLine ? 'Creating strumline #$strumLineID' : 'Editing strumline #$strumLineID properties';
winTitle = TU.translate("charterStrumlineScreen.win-title" + (creatingStrumLine ? "-creating" : "-editing"), ["#" + strumLineID]);
winWidth = 720; winHeight = 400;
FlxG.sound.music.pause();
@@ -69,26 +69,26 @@ class CharterStrumlineScreen extends UISubstateWindow {
add(new UIText(ui.x, ui.y - 24, 0, text));
var title:UIText;
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, creatingStrumLine ? "Create New Strumline" : "Edit Strumline Properties", 28));
add(title = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, TU.translate("charterStrumlineScreen.title" + (creatingStrumLine ? "-creating" : "-editing")), 28));
var charFileList = Character.getList(true);
if (charFileList.length == 0) charFileList = Character.getList(false);
charactersList = new UIButtonList<CharacterButton>(15, title.y + title.height + 36, 250, 269, null, FlxPoint.get(250, 54), null, 0);
charactersList.addButton.callback = () -> charactersList.add(new CharacterButton(0, 0, "New Char", charFileList, charactersList));
charactersList.addButton.callback = () -> charactersList.add(new CharacterButton(0, 0, TU.translate("charterStrumLine.newChar"), charFileList, charactersList));
charactersList.cameraSpacing = 0;
for (i in strumLine.characters)
charactersList.add(new CharacterButton(0, 0, i, charFileList, charactersList));
add(charactersList);
addLabelOn(charactersList, "Characters");
addLabelOn(charactersList, TU.translate("charterStrumLine.characters"));
charactersList.frames = Paths.getFrames('editors/ui/inputbox');
typeDropdown = new UIDropDown(charactersList.x + charactersList.bWidth + 16, charactersList.y, 200, 32, ["OPPONENT", "PLAYER", "ADDITIONAL"], strumLine.type);
add(typeDropdown);
addLabelOn(typeDropdown, "Type");
addLabelOn(typeDropdown, TU.translate("charterStrumLine.type"));
usesChartScrollSpeed = new UICheckbox(typeDropdown.x + 104, typeDropdown.y + 135, "Uses charts scroll speed?", strumLine.scrollSpeed == null);
usesChartScrollSpeed = new UICheckbox(typeDropdown.x + 104, typeDropdown.y + 135, TU.translate("charterStrumLine.useChartScrollSpeed"), strumLine.scrollSpeed == null);
usesChartScrollSpeed.onChecked = function(b) {
if(b)
{
@@ -108,21 +108,21 @@ class CharterStrumlineScreen extends UISubstateWindow {
scrollSpeedStepper.selectable = true;
}
add(scrollSpeedStepper);
addLabelOn(scrollSpeedStepper, "Scroll Speed");
addLabelOn(scrollSpeedStepper, TU.translate("charterStrumLine.scrollSpeed"));
var stagePositionI = strumLine.position == null ? strumLine.type : ["DAD", "BOYFRIEND", "GIRLFRIEND"].indexOf(strumLine.position.toUpperCase());
stagePositionDropdown = new UIDropDown(typeDropdown.x + 200 - 32 + 26, typeDropdown.y, 200, 32, ["DAD", "BOYFRIEND", "GIRLFRIEND"], stagePositionI);
add(stagePositionDropdown);
addLabelOn(stagePositionDropdown, "Stage Position");
addLabelOn(stagePositionDropdown, TU.translate("charterStrumLine.stagePos"));
hudScaleStepper = new UINumericStepper(typeDropdown.x, typeDropdown.y + 64, strumLine.strumScale == null ? 1 : strumLine.strumScale, 0.001, 2, null, null, 84);
add(hudScaleStepper);
addLabelOn(hudScaleStepper, "Scale");
addLabelOn(hudScaleStepper, TU.translate("charterStrumLine.scale"));
hudSpacingStepper = new UINumericStepper(hudScaleStepper.x + 80 - 32 + 26, hudScaleStepper.y, strumLine.strumSpacing != null ? strumLine.strumSpacing : 1, 0.001, 10, null, null, 84);
add(hudSpacingStepper);
addLabelOn(hudSpacingStepper, "Spacing");
addLabelOn(hudSpacingStepper, TU.translate("charterStrumLine.spacing"));
var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.type == 1 ? 0.75 : 0.25) : strumLine.strumLinePos;
@@ -132,23 +132,23 @@ class CharterStrumlineScreen extends UISubstateWindow {
hudXStepper = new UINumericStepper(hudSpacingStepper.x + 80, hudScaleStepper.y, startingPos.x, 0.01, 2, 0, 2, 84);
add(hudXStepper);
addLabelOn(hudXStepper, "Hud Position (X [Ratio 0-1],Y)");
addLabelOn(hudXStepper, TU.translate("charterStrumLine.hudPos"));
add(new UIText(hudXStepper.x + 84 - 32 + 0, hudXStepper.y + 9, 0, ",", 22));
hudYStepper = new UINumericStepper(hudXStepper.x + 84 - 32 + 26, hudXStepper.y, startingPos.y, 0.001, 2, null, null, 84);
add(hudYStepper);
visibleCheckbox = new UICheckbox(hudYStepper.x + hudYStepper.bWidth + 42, hudYStepper.y + 9, "Visible?", strumLine.visible);
visibleCheckbox = new UICheckbox(hudYStepper.x + hudYStepper.bWidth + 42, hudYStepper.y + 9, TU.translate("charterStrumLine.visible"), strumLine.visible);
add(visibleCheckbox);
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
saveStrumline();
close();
}, 125);
add(saveButton);
closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, creatingStrumLine ? "Cancel" : "Close", function() {
closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor." + (creatingStrumLine ? "cancel" : "close")), function() {
if (creatingStrumLine && onSave != null) onSave(null);
close();
}, 125);
@@ -170,12 +170,12 @@ class CharterStrumlineScreen extends UISubstateWindow {
vocalsSuffixDropDown = new UIDropDown(typeDropdown.x, hudScaleStepper.y + 128, 200, 32, suffixList, strumLine.vocalsSuffix != null && strumLine.vocalsSuffix != "" ? suffixList.indexOf(strumLine.vocalsSuffix) : 0);
add(vocalsSuffixDropDown);
addLabelOn(vocalsSuffixDropDown, "Vocal Suffix");
addLabelOn(vocalsSuffixDropDown, TU.translate("charterStrumLine.vocalSuffix"));
keyCountStepper = new UINumericStepper(stagePositionDropdown.x, vocalsSuffixDropDown.y, strumLine.keyCount != null ? strumLine.keyCount : 4, 1, 0, 1, 1000, 84);
// if (Flags.CHARTER_ADVANCED_SETTINGS) {
add(keyCountStepper);
addLabelOn(keyCountStepper, "Key Count");
addLabelOn(keyCountStepper, TU.translate("charterStrumLine.keyCount"));
// }
strumLineCam = new HudCamera();
@@ -242,7 +242,8 @@ class CharacterButton extends UIButton {
super(x, y, null, null, 250, 54);
charIcon = new HealthIcon(Character.getIconFromCharName(char));
charIcon.scale.set(0.3, 0.3);
var size = Std.int(150 * 0.3);
charIcon.setUnstretchedGraphicSize(size, size, true);
charIcon.updateHitbox();
charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2);
charIcon.scrollFactor.set(1,1);
@@ -254,11 +255,10 @@ class CharacterButton extends UIButton {
textBox.antialiasing = true;
textBox.onChange = function(char:String) {
char = Character.getIconFromCharName(char);
var image = Paths.image("icons/" + char);
if(!Assets.exists(image))
image = Paths.image("icons/" + Flags.DEFAULT_HEALTH_ICON);
charIcon.loadGraphic(image, true, 150, 150);
charIcon.setIcon(char);
charIcon.setUnstretchedGraphicSize(size, size, true);
charIcon.updateHitbox();
charIcon.setPosition(x + 10, bHeight/2 - charIcon.height / 2);
}
deleteButton = new UIButton(textBox.x + 115 + 16, bHeight/2 - (32/2), null, function () {
@@ -29,7 +29,7 @@ class CharterWaveformHandler extends FlxBasic {
public function generateData(name:String, sound:FlxSound):BitmapData {
if (!sounds.exists(name)) sounds.set(name, sound);
if (!analyzers.exists(name))
if (!analyzers.exists(name))
analyzers.set(name, new AudioAnalyzer(sound));
var analyzer:AudioAnalyzer = analyzers.get(name);
@@ -3,33 +3,53 @@ package funkin.editors.charter;
import flixel.group.FlxGroup;
import flixel.text.FlxText.FlxTextFormat;
import flixel.text.FlxText.FlxTextFormatMarkerPair;
import funkin.backend.chart.ChartData.ChartMetaData;
import funkin.backend.chart.ChartData;
import funkin.backend.chart.FNFLegacyParser.SwagSong;
import funkin.backend.chart.PsychParser;
import funkin.backend.chart.VSliceParser;
import funkin.backend.utils.ZipUtil.ZipReader;
import funkin.game.HealthIcon;
import haxe.Json;
import haxe.io.Bytes;
import haxe.io.BytesInput;
typedef SongCreationData = {
var meta:ChartMetaData;
var instBytes:Bytes;
var voicesBytes:Bytes;
@:optional var playerVocals:Bytes;
@:optional var oppVocals:Bytes;
}
class SongCreationScreen extends UISubstateWindow {
private var onSave:Null<SongCreationData> -> Void = null;
private var onSave:Null<SongCreationData> -> Null<String -> SongCreationData -> Void> -> Void = null;
public var songNameTextBox:UITextBox;
public var bpmStepper:UINumericStepper;
public var beatsPerMeasureStepper:UINumericStepper;
public var stepsPerBeatStepper :UINumericStepper;
public var denominatorStepper:UINumericStepper;
public var instExplorer:UIFileExplorer;
public var voicesExplorer:UIFileExplorer;
public var importFrom:UIButton;
public var displayNameTextBox:UITextBox;
public var iconTextBox:UITextBox;
public var iconSprite:FlxSprite;
public var iconSprite:HealthIcon;
public var opponentModeCheckbox:UICheckbox;
public var coopAllowedCheckbox:UICheckbox;
public var colorWheel:UIColorwheel;
public var difficultiesTextBox:UITextBox;
public var engineDropdown:UIDropDown;
public var createSong:UIButton;
public var importInstExplorer:UIFileExplorer;
public var importVoicesExplorer:UIFileExplorer;
public var importIdTextBox:UITextBox;
public var importChartFile:UIFileExplorer;
public var importMetaFile:UIFileExplorer;
public var backButton:UIButton;
public var saveButton:UIButton;
public var closeButton:UIButton;
@@ -37,17 +57,33 @@ class SongCreationScreen extends UISubstateWindow {
public var songDataGroup:FlxGroup = new FlxGroup();
public var menuDataGroup:FlxGroup = new FlxGroup();
public var selectFormatGroup:FlxGroup = new FlxGroup();
public var importAudioGroup:FlxGroup = new FlxGroup();
public var importDataGroup:FlxGroup = new FlxGroup();
public var pages:Array<FlxGroup> = [];
public var pageSizes:Array<FlxPoint> = [];
public var importPages:Array<FlxGroup> = [];
public var importPageSizes:Array<FlxPoint> = [];
public var isImporting = false;
public var curPage:Int = 0;
public function new(?onSave:SongCreationData->Void) {
public function new(?onSave:SongCreationData -> Null<String -> SongCreationData -> Void> -> Void) {
super();
if (onSave != null) this.onSave = onSave;
}
inline function translate(id:String):String
return TU.translate("songCreationScreen." + id);
inline function translateMeta(id:String):String
return TU.translate("charterMetaDataScreen." + id);
public override function create() {
winTitle = "Creating New Song";
winTitle = translate("win-title");
winWidth = 748 - 32 + 40;
winHeight = 520;
@@ -55,32 +91,30 @@ class SongCreationScreen extends UISubstateWindow {
super.create();
function addLabelOn(ui:UISprite, text:String):UIText {
var text:UIText = new UIText(ui.x, ui.y - 24, 0, text);
ui.members.push(text);
return text;
var label:UIText = new UIText(ui.x, ui.y - 24, 0, text);
ui.members.push(label);
return label;
}
var songTitle:UIText;
songDataGroup.add(songTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Song Info", 28));
songDataGroup.add(songTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("title"), 28));
songNameTextBox = new UITextBox(songTitle.x, songTitle.y + songTitle.height + 36, "Song Name");
songNameTextBox = new UITextBox(songTitle.x, songTitle.y + songTitle.height + 36, translateMeta("songName"));
songDataGroup.add(songNameTextBox);
addLabelOn(songNameTextBox, "Song Name");
addLabelOn(songNameTextBox, translateMeta("songName"));
bpmStepper = new UINumericStepper(songNameTextBox.x + 320 + 26, songNameTextBox.y, 100, 1, 2, 1, null, 90);
songDataGroup.add(bpmStepper);
addLabelOn(bpmStepper, "BPM");
addLabelOn(bpmStepper, translateMeta("bpm"));
beatsPerMeasureStepper = new UINumericStepper(bpmStepper.x + 60 + 26, bpmStepper.y, 4, 1, 0, 1, null, 54);
songDataGroup.add(beatsPerMeasureStepper);
addLabelOn(beatsPerMeasureStepper, "Time Signature");
addLabelOn(beatsPerMeasureStepper, translateMeta("timeSignature"));
songDataGroup.add(new UIText(beatsPerMeasureStepper.x + 30, beatsPerMeasureStepper.y + 3, 0, "/", 22));
stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54);
songDataGroup.add(stepsPerBeatStepper);
var voicesUIText:UIText = null;
denominatorStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54);
songDataGroup.add(denominatorStepper);
instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Flags.SOUND_EXT, function (path, res) {
if (path == null || res == null) return;
@@ -89,8 +123,8 @@ class SongCreationScreen extends UISubstateWindow {
instExplorer.uiElement = audioPlayer;
});
songDataGroup.add(instExplorer);
addLabelOn(instExplorer, "Inst Audio File").applyMarkup(
"Inst Audio File $* Required$",
addLabelOn(instExplorer, "").applyMarkup(
translate("instAudio"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
voicesExplorer = new UIFileExplorer(instExplorer.x + 320 + 26, instExplorer.y, null, null, Flags.SOUND_EXT, function (path, res) {
@@ -100,41 +134,114 @@ class SongCreationScreen extends UISubstateWindow {
voicesExplorer.uiElement = audioPlayer;
});
songDataGroup.add(voicesExplorer);
addLabelOn(voicesExplorer, "Vocal Audio File");
/*voicesUIText = addLabelOn(voicesExplorer, "");
voicesUIText.applyMarkup(
translate("voicesAudio"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);*/
addLabelOn(voicesExplorer, translate("voicesAudio"));
importFrom = new UIButton(windowSpr.x + 20, windowSpr.y + windowSpr.bHeight - 16 - 32, translate("importFrom"), function() {
winTitle = translate("win-title-importing");
isImporting = true;
updatePagesTexts();
refreshPages();
}, 150);
songDataGroup.add(importFrom);
var menuTitle:UIText;
menuDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Menus Data (Freeplay/Story)", 28));
menuDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translateMeta("menusData"), 28));
displayNameTextBox = new UITextBox(menuTitle.x, menuTitle.y + menuTitle.height + 36, "Display Name");
displayNameTextBox = new UITextBox(menuTitle.x, menuTitle.y + menuTitle.height + 36, translateMeta("displayName"));
menuDataGroup.add(displayNameTextBox);
addLabelOn(displayNameTextBox, "Display Name");
addLabelOn(displayNameTextBox, translateMeta("displayName"));
iconTextBox = new UITextBox(displayNameTextBox.x + 320 + 26, displayNameTextBox.y, "Icon", 150);
iconTextBox = new UITextBox(displayNameTextBox.x + 320 + 26, displayNameTextBox.y, "face", 150);
iconTextBox.onChange = (newIcon:String) -> {updateIcon(newIcon);}
menuDataGroup.add(iconTextBox);
addLabelOn(iconTextBox, "Icon");
addLabelOn(iconTextBox, translateMeta("icon"));
updateIcon("Icon");
updateIcon("face");
opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, "Opponent Mode", true);
opponentModeCheckbox = new UICheckbox(displayNameTextBox.x, iconTextBox.y + 10 + 32 + 26, translateMeta("opponentMode"), true);
menuDataGroup.add(opponentModeCheckbox);
addLabelOn(opponentModeCheckbox, "Modes Allowed");
addLabelOn(opponentModeCheckbox, translateMeta("modesAllowed"));
coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, "Co-op Mode", true);
coopAllowedCheckbox = new UICheckbox(opponentModeCheckbox.x + 150 + 26, opponentModeCheckbox.y, translateMeta("coopAllowed"), true);
menuDataGroup.add(coopAllowedCheckbox);
colorWheel = new UIColorwheel(iconTextBox.x, coopAllowedCheckbox.y, 0xFFFFFF);
menuDataGroup.add(colorWheel);
addLabelOn(colorWheel, "Color");
addLabelOn(colorWheel, translateMeta("color"));
difficultiesTextBox = new UITextBox(opponentModeCheckbox.x, opponentModeCheckbox.y + 6 + 32 + 26, "");
menuDataGroup.add(difficultiesTextBox);
addLabelOn(difficultiesTextBox, "Difficulties");
addLabelOn(difficultiesTextBox, translateMeta("difficulties"));
for (checkbox in [opponentModeCheckbox, coopAllowedCheckbox])
{checkbox.y += 6; checkbox.x += 4;}
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
var menuTitle:UIText;
selectFormatGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("importSource"), 28));
engineDropdown = new UIDropDown(menuTitle.x, menuTitle.y + menuTitle.height + 36, 480, 32, [translate("legacyOrPsych"), translate("vslice"), translate("vsliceProject")]);
selectFormatGroup.add(engineDropdown);
addLabelOn(engineDropdown, translate("importChartFormat"));
createSong = new UIButton(windowSpr.x + 20, windowSpr.y + windowSpr.bHeight - 16 - 32, "< " + translate("back"), function() {
winTitle = translate("win-title");
isImporting = false;
updatePagesTexts();
refreshPages();
}, 120);
selectFormatGroup.add(createSong);
var menuTitle:UIText;
importAudioGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("importAudios"), 28));
importInstExplorer = new UIFileExplorer(menuTitle.x, menuTitle.y + menuTitle.height + 36, null, null, Flags.SOUND_EXT, function (path, res) {
if (path == null || res == null) return;
var audioPlayer:UIAudioPlayer = new UIAudioPlayer(importInstExplorer.x + 8, importInstExplorer.y + 8, res);
importInstExplorer.members.push(audioPlayer);
importInstExplorer.uiElement = audioPlayer;
});
importAudioGroup.add(importInstExplorer);
addLabelOn(importInstExplorer, "").applyMarkup(
translate("instAudio"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
importVoicesExplorer = new UIFileExplorer(importInstExplorer.x + 320 + 26, importInstExplorer.y, null, null, Flags.SOUND_EXT, function (path, res) {
if (path == null || res == null) return;
var audioPlayer:UIAudioPlayer = new UIAudioPlayer(importVoicesExplorer.x + 8, importVoicesExplorer.y + 8, res);
importVoicesExplorer.members.push(audioPlayer);
importVoicesExplorer.uiElement = audioPlayer;
});
importAudioGroup.add(importVoicesExplorer);
addLabelOn(importVoicesExplorer, translate("voicesAudio"));
var menuTitle:UIText;
importDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Add Data", 28));
importDataGroup.add(importIdTextBox = new UITextBox(menuTitle.x, menuTitle.y + menuTitle.height + 36));
addLabelOn(importIdTextBox, "").applyMarkup(
translate("songFileName"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
importChartFile = new UIFileExplorer(importIdTextBox.x, importIdTextBox.y + importIdTextBox.height + 56, null, null, "fnfc", function (_, _) importIdTextBox.label.text = new haxe.io.Path(importChartFile.filePath).file);
importDataGroup.add(importChartFile);
addLabelOn(importChartFile, "").applyMarkup(
translate("songDataFile"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
importMetaFile = new UIFileExplorer(importChartFile.x + 320 + 26, importChartFile.y, null, null, "json");
importDataGroup.add(importMetaFile);
addLabelOn(importMetaFile, "").applyMarkup(
translate("songMetaFile"),
[new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
var pages = isImporting ? importPages : pages;
if (curPage == pages.length-1) {
saveSongInfo();
close();
@@ -147,7 +254,7 @@ class SongCreationScreen extends UISubstateWindow {
}, 125);
add(saveButton);
backButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "< Back", function() {
backButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "< " + translate("back"), function() {
curPage--;
refreshPages();
@@ -155,7 +262,7 @@ class SongCreationScreen extends UISubstateWindow {
}, 125);
add(backButton);
closeButton = new UIButton(backButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", function() {
closeButton = new UIButton(backButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.close"), function() {
close();
}, 125);
add(closeButton);
@@ -167,17 +274,41 @@ class SongCreationScreen extends UISubstateWindow {
pages.push(cast add(menuDataGroup));
pageSizes.push(FlxPoint.get(748 - 32 + 40, 400));
importPages.push(cast add(selectFormatGroup));
importPageSizes.push(FlxPoint.get(500, 250));
importPages.push(cast add(importAudioGroup));
importPageSizes.push(FlxPoint.get(705, 250));
importPages.push(cast add(importDataGroup));
importPageSizes.push(FlxPoint.get(705, 300));
refreshPages();
updatePagesTexts();
}
public override function update(elapsed:Float) {
if (curPage == 0) {
if (instExplorer.file != null)
if (isImporting)
{
var name = engineDropdown.options[engineDropdown.index];
var project = name == translate("vsliceProject");
if (curPage == 1) {
importInstExplorer.selectable = importVoicesExplorer.selectable = !project;
saveButton.selectable = project ? true : (importInstExplorer.file != null);
} else if (curPage == 2) {
importIdTextBox.selectable = !project;
importChartFile.fileType = project ? "fnfc" : "json";
importMetaFile.selectable = name == translate("vslice");
saveButton.selectable = importChartFile.file != null && (!importMetaFile.selectable || importMetaFile.file != null) && (!importIdTextBox.selectable || importIdTextBox.label.text.trim().length > 0);
} else
saveButton.selectable = true;
else saveButton.selectable = false;
} else
saveButton.selectable = true;
} else {
if (curPage == 0)
saveButton.selectable = (instExplorer.file != null);
else
saveButton.selectable = true;
}
saveButton.alpha = saveButton.field.alpha = saveButton.selectable ? 1 : 0.4;
super.update(elapsed);
@@ -185,70 +316,145 @@ class SongCreationScreen extends UISubstateWindow {
function refreshPages() {
for (i=>page in pages)
page.visible = page.exists = i == curPage;
page.visible = page.exists = i == curPage && !isImporting;
for (i=>page in importPages)
page.visible = page.exists = i == curPage && isImporting;
}
function updatePagesTexts() {
var pageSizes = isImporting ? importPageSizes : pageSizes;
var pages = isImporting ? importPages : pages;
windowSpr.bWidth = Std.int(pageSizes[curPage].x);
windowSpr.bHeight = Std.int(pageSizes[curPage].y);
titleSpr.x = windowSpr.x + 25;
titleSpr.y = windowSpr.y + ((30 - titleSpr.height) / 2);
saveButton.field.text = curPage == pages.length-1 ? "Save & Close" : 'Next >';
titleSpr.text = 'Creating New Song (${curPage+1}/${pages.length})';
saveButton.field.text = curPage == pages.length-1 ? TU.translate("editor.saveClose") : translate("next") + ' >';
titleSpr.text = '$winTitle (${curPage+1}/${pages.length})';
backButton.field.text = '< Back';
backButton.field.text = '< ' + translate("back");
backButton.visible = backButton.exists = curPage > 0;
backButton.x = (saveButton.x = windowSpr.x + windowSpr.bWidth - 20 - 125) - 20 - saveButton.bWidth;
closeButton.x = (curPage > 0 ? backButton : saveButton).x - 20 - saveButton.bWidth;
for (button in [saveButton, backButton, closeButton])
for (button in [saveButton, backButton, closeButton, importFrom, createSong])
button.y = windowSpr.y + windowSpr.bHeight - 16 - 32;
}
function updateIcon(icon:String) {
if (iconSprite == null) menuDataGroup.add(iconSprite = new FlxSprite());
if (iconSprite == null) menuDataGroup.add(iconSprite = new HealthIcon());
if (iconSprite.animation.exists(icon)) return;
@:privateAccess iconSprite.animation.clearAnimations();
var path:String = Paths.image('icons/$icon');
if (!Assets.exists(path)) path = Paths.image('icons/' + Flags.DEFAULT_HEALTH_ICON);
iconSprite.loadGraphic(path, true, 150, 150);
iconSprite.animation.add(icon, [0], 0, false);
iconSprite.antialiasing = true;
iconSprite.animation.play(icon);
iconSprite.scale.set(0.5, 0.5);
iconSprite.setIcon(icon);
var size = Std.int(150 * 0.5);
iconSprite.setUnstretchedGraphicSize(size, size, true);
iconSprite.updateHitbox();
iconSprite.setPosition(iconTextBox.x + 150 + 8, (iconTextBox.y + 16) - (iconSprite.height/2));
iconSprite.setPosition(iconTextBox.x + iconTextBox.bWidth + 8, iconTextBox.y + (iconTextBox.bHeight / 2) - (iconSprite.height / 2));
iconSprite.scrollFactor.set(1, 1);
}
function saveSongInfo() {
UIUtil.confirmUISelections(this);
if (isImporting)
{
try switch(engineDropdown.index)
{
case 0 /*"V-Slice Project (.fnfc)"*/:
var files:Map<String, Any> = [];
for (field in new ZipReader(new BytesInput(importChartFile.file)).read()) {
var fileName = field.fileName;
var fileContent = field.data;
files.set(fileName, fileContent);
}
saveFromVSlice(files);
case 1 /*"V-Slice"*/:
var songId = importIdTextBox.label.text;
var files:Map<String, Any> = [];
files.set('${songId}-metadata.json', importMetaFile.file);
files.set('${songId}-chart.json', importChartFile.file);
files.set('Inst.${Flags.SOUND_EXT}', importInstExplorer.file);
files.set('Voices.${Flags.SOUND_EXT}', importVoicesExplorer.file);
saveFromVSlice(files, songId);
default /*"Psych/Legacy FNF"*/:
var songId = importIdTextBox.label.text;
var oldChart:SwagSong = Json.parse(cast importChartFile.file);
var base:ChartData = {
strumLines: [],
noteTypes: [],
events: [],
meta: {name: null},
scrollSpeed: Flags.DEFAULT_SCROLL_SPEED,
stage: Flags.DEFAULT_STAGE,
codenameChart: true
};
PsychParser.parse(oldChart, base);
if (onSave != null) onSave({
meta: {
name: songId,
difficulties: [songId],
bpm: oldChart.bpm,
beatsPerMeasure: oldChart.beatsPerMeasure,
stepsPerBeat: oldChart.stepsPerBeat,
displayName: oldChart.song
},
instBytes: importInstExplorer.file,
voicesBytes: importVoicesExplorer.file,
}, (songFolder:String, creation:SongCreationData) -> {
#if sys
CoolUtil.safeSaveFile('$songFolder/charts/${songId}.json', Json.stringify(base, Flags.JSON_PRETTY_PRINT));
#end
});
} catch (e) {
openSubState(new UIWarningSubstate("Importing Song/Charts: Error!", Std.string(e), [
{label: "Ok", color: 0xFFFF0000, onClick: function(t) {}}
]));
}
} else {
for (stepper in [bpmStepper, beatsPerMeasureStepper, denominatorStepper])
@:privateAccess stepper.__onChange(stepper.label.text);
var meta:ChartMetaData = {
name: songNameTextBox.label.text,
bpm: bpmStepper.value,
beatsPerMeasure: Std.int(beatsPerMeasureStepper.value),
stepsPerBeat: Std.int(stepsPerBeatStepper.value),
displayName: displayNameTextBox.label.text,
icon: iconTextBox.label.text,
color: colorWheel.curColorString,
parsedColor: colorWheel.curColor,
opponentModeAllowed: opponentModeCheckbox.checked,
coopAllowed: coopAllowedCheckbox.checked,
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()],
};
var meta:ChartMetaData = {
name: songNameTextBox.label.text,
bpm: bpmStepper.value,
beatsPerMeasure: Std.int(beatsPerMeasureStepper.value),
stepsPerBeat: Std.int(16 / denominatorStepper.value),
displayName: displayNameTextBox.label.text,
icon: iconTextBox.label.text,
color: colorWheel.curColor,
opponentModeAllowed: opponentModeCheckbox.checked,
coopAllowed: coopAllowedCheckbox.checked,
difficulties: [for (diff in difficultiesTextBox.label.text.split(",")) diff.trim()],
};
if (onSave != null) onSave({
meta: meta,
instBytes: instExplorer.file,
voicesBytes: voicesExplorer.file
}, null);
}
}
function saveFromVSlice(files:Map<String, Any>, ?name:String) {
var songId = name == null && files.exists("manifest.json") ? Json.parse(files.get("manifest.json")).songId : name;
var vslicemeta:SwagMetadata = Json.parse(files.get('${songId}-metadata.json'));
var vslicechart:NewSwagSong = Json.parse(files.get('${songId}-chart.json'));
var playData = vslicemeta.playData;
var meta:ChartMetaData = {name: songId};
var diffCharts:Array<ChartDataWithInfo> = [];
VSliceParser.parse(vslicemeta, vslicechart, meta, diffCharts, songId);
if (onSave != null) onSave({
meta: meta,
instBytes: instExplorer.file,
voicesBytes: voicesExplorer.file
instBytes: files.get('Inst.${Flags.SOUND_EXT}'),
voicesBytes: files.get('Voices.${Flags.SOUND_EXT}'), //it may exist
playerVocals: files.get('Voices-${playData.characters.playerVocals != null ? playData.characters.playerVocals[0] : playData.characters.player}.${Flags.SOUND_EXT}'),
oppVocals: files.get('Voices-${playData.characters.opponentVocals != null ? playData.characters.opponentVocals[0] : playData.characters.opponent}.${Flags.SOUND_EXT}'),
}, (songFolder:String, creation:SongCreationData) -> {
#if sys
for (diff in diffCharts) CoolUtil.safeSaveFile('$songFolder/charts/${diff.diffName}.json', Json.stringify(diff.chart, Flags.JSON_PRETTY_PRINT));
#end
});
}
}
@@ -20,8 +20,11 @@ class StageCreationScreen extends UISubstateWindow {
if (onSave != null) this.onSave = onSave;
}
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("stageCreationScreen." + id, args);
public override function create() {
winTitle = "Creating New Stage";
winTitle = translate("win-title");
winWidth = 748 - 32 + 40;
winHeight = 220;
@@ -35,20 +38,20 @@ class StageCreationScreen extends UISubstateWindow {
}
var stageInfo:UIText;
add(stageInfo = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, "Stage Info", 28));
add(stageInfo = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translate("title"), 28));
add(stageNameTextBox = new UITextBox(stageInfo.x, stageInfo.y + stageInfo.height + 36, "Stage Name"));
addLabelOn(stageNameTextBox, "Stage Name");
add(stageNameTextBox = new UITextBox(stageInfo.x, stageInfo.y + stageInfo.height + 36, translate("stage-name")));
addLabelOn(stageNameTextBox, translate("stage-name"));
add(stagePathTextBox = new UITextBox(stageNameTextBox.x + 320 + 26, stageNameTextBox.y, "Stage Path"));
addLabelOn(stagePathTextBox, "Stage Path");
add(stagePathTextBox = new UITextBox(stageNameTextBox.x + 320 + 26, stageNameTextBox.y, translate("stage-path")));
addLabelOn(stagePathTextBox, translate("stage-path"));
add(saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, "Save & Close", function() {
add(saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20 - 125, windowSpr.y + windowSpr.bHeight - 16 - 32, TU.translate("editor.saveClose"), function() {
saveStageInfo();
close();
}, 125));
add(closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, "Cancel", close, 125));
add(closeButton = new UIButton(saveButton.x - 20 - saveButton.bWidth, saveButton.y, TU.translate("editor.cancel"), close, 125));
closeButton.color = 0xFFFF0000;
}
+41 -28
View File
@@ -71,6 +71,9 @@ class StageEditor extends UIState {
return "stageEditor." + id;
}
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("stageEditor." + id, args);
public function new(stage:String) {
super();
if (stage != null) __stage = stage;
@@ -79,47 +82,47 @@ class StageEditor extends UIState {
public override function create() {
super.create();
WindowUtils.suffix = " (Stage Editor)";
WindowUtils.suffix = " (" + TU.translate("editor.stage.name") + ")";
SaveWarning.selectionClass = StageSelection;
SaveWarning.saveFunc = () -> {_file_save(null);};
topMenu = [
{
label: "File",
label: translate("topBar.file"),
childs: [
{
label: "Save",
label: translate("file.save"),
keybind: [CONTROL, S],
onSelect: _file_save,
},
{
label: "Save As...",
label: translate("file.saveAs"),
keybind: [CONTROL, SHIFT, S],
onSelect: _file_saveas,
},
null,
{
label: "Exit",
label: translate("file.exit"),
onSelect: _file_exit
}
]
},
{
label: "Edit",
label: translate("topBar.edit"),
childs: [
{
label: "Undo",
label: translate("edit.undo"),
keybind: [CONTROL, Z],
onSelect: _edit_undo,
},
{
label: "Redo",
label: translate("edit.redo"),
keybind: [CONTROL, SHIFT, Z],
onSelect: _edit_redo,
},
null,
{
label: "Edit Stage Info",
label: translate("edit.editStageInfo"),
onSelect: (_) -> {
openSubState(new UISoftcodedWindow(
"layouts/stage/stageInfoScreen",
@@ -130,7 +133,10 @@ class StageEditor extends UIState {
"stage" => stage,
"Stage" => Stage,
"exID" => exID,
"CEditInfo" => StageChange.CEditInfo
"CEditInfo" => StageChange.CEditInfo,
"translate" => function (id:String, ?args:Array<Dynamic>) {
return TU.translate("stageInfoScreen." + id, args);
}
]
));
},
@@ -138,56 +144,56 @@ class StageEditor extends UIState {
]
},
{
label: "Select",
label: translate("topBar.select"),
childs: [
{
label: "All",
label: translate("select.all"),
keybind: [CONTROL, A],
onSelect: (_) -> _select_all(_),
},
{
label: "Deselect",
label: translate("select.deselect"),
keybind: [CONTROL, D],
onSelect: (_) -> _select_deselect(_),
},
{
label: "Inverse",
label: translate("select.inverse"),
keybind: [CONTROL, SHIFT, I],
onSelect: (_) -> _select_inverse(_),
},
]
},
{
label: "View",
label: translate("topBar.view"),
childs: [
{
label: "Zoom in",
label: translate("view.zoomIn"),
keybind: [CONTROL, NUMPADPLUS],
onSelect: _view_zoomin
},
{
label: "Zoom out",
label: translate("view.zoomOut"),
keybind: [CONTROL, NUMPADMINUS],
onSelect: _view_zoomout
},
{
label: "Reset zoom",
label: translate("view.zoomReset"),
keybind: [CONTROL, NUMPADZERO],
onSelect: _view_zoomreset
},
null,
{
label: "Focus Dad",
label: translate("view.focusDad"),
//keybind: [CONTROL, NUMPADZERO],
onSelect: _view_focusdad
},
{
label: "Focus Gf",
label: translate("view.focusGF"),
//keybind: [CONTROL, NUMPADZERO],
onSelect: _view_focusgf
},
{
label: "Focus BF",
label: translate("view.focusBF"),
//keybind: [CONTROL, NUMPADZERO],
onSelect: _view_focusbf
},
@@ -195,10 +201,10 @@ class StageEditor extends UIState {
]
},
{
label: "Editor",
label: translate("topBar.editor"),
childs: [
{
label: "Show Characters",
label: translate("editor.showCharacters"),
//keybind: [CONTROL, NUMPADMINUS],
onSelect: _editor_showCharacters,
icon: showCharacters ? 1 : 0
@@ -502,6 +508,7 @@ class StageEditor extends UIState {
function _file_save(_) {
#if sys
FlxG.sound.play(Paths.sound('editors/save'));
CoolUtil.safeSaveFile(
'${Paths.getAssetsRoot()}/data/stages/${__stage}.xml',
buildStage()
@@ -513,6 +520,7 @@ class StageEditor extends UIState {
}
function _file_saveas(_) {
FlxG.sound.play(Paths.sound('editors/save'));
openSubState(new SaveSubstate(buildStage(), {
defaultSaveFile: '${__stage}.xml'
}));
@@ -719,12 +727,12 @@ class StageEditor extends UIState {
}
}
Logs.trace(Printer.print(xml, true));
return "<!DOCTYPE codename-engine-stage>\n" + Printer.print(xml, true);
var xmlThingYea:String = "<!DOCTYPE codename-engine-stage>\n" + Printer.print(xml, Options.editorPrettyPrint);
return Options.editorPrettyPrint ? xmlThingYea : xmlThingYea.replace("\n", "");
}
function _edit_undo(_) {
FlxG.sound.play(Flags.DEFAULT_EDITOR_UNDO_SOUND);
var undo = undos.undo();
switch(undo) {
case null:
@@ -750,6 +758,7 @@ class StageEditor extends UIState {
}
function _edit_redo(_) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_REDO_SOUND));
var redo = undos.redo();
switch(redo) {
case null:
@@ -810,7 +819,7 @@ class StageEditor extends UIState {
var sprite:FunkinSprite = cast sprite;
sprite.extra.set(exID("selected"), true);
sprite.extra.get(exID("button")).selected = true;
Logs.trace("Selected " + sprite.name);
//Logs.trace("Selected " + sprite.name);
}
}
}
@@ -1352,6 +1361,9 @@ class StageXMLEditScreen extends UISoftcodedWindow {
public var xml:Access;
public var saveCallback:Void->Void;
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("stageXMLEditScreen." + id, args);
public function new(xml:Access, saveCallback:Void->Void, type:String = "Unknown") {
this.xml = xml;
this.saveCallback = saveCallback;
@@ -1359,7 +1371,8 @@ class StageXMLEditScreen extends UISoftcodedWindow {
"stage" => StageEditor.instance.stage,
"xml" => xml,
"exID" => StageEditor.exID,
"type" => type
"type" => type,
"translate" => translate
]);
}
@@ -21,20 +21,20 @@ class StageSelection extends EditorTreeMenu
var list:Array<OptionType> = [
for (stage in (modsList.length == 0 ? Stage.getList(false, true) : modsList)) {
stages.push(stage);
new TextOption(stage, "Press ACCEPT to edit this stage.",
new TextOption(stage, TU.translate("stageSelection.acceptStage"),
function() {
FlxG.switchState(new StageEditor(stage));
});
}
];
list.insert(0, new NewOption("New Stage", "Press ACCEPT to create a new stage.", function() {
list.insert(0, new NewOption(TU.translate("stageSelection.newStage"), TU.translate("stageSelection.acceptNewStage"), function() {
FlxG.state.openSubState(new StageCreationScreen(saveStage));
}));
main = new OptionsScreen("Stage Editor", "Select a stage to edit. HSCRIPT only stages are not supported.", list);
main = new OptionsScreen(TU.translate("editor.stage.name"), TU.translate("stageSelection.desc"), list);
//DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]);
DiscordUtil.call("onEditorTreeLoaded", ["Stage Editor"]);
}
public function saveStage(creation:StageCreationData) {
@@ -51,7 +51,7 @@ class StageSelection extends EditorTreeMenu
#end
// Add to List
var option = new TextOption(creation.name, "Press ACCEPT to edit this stage.", function() {
var option = new TextOption(creation.name, TU.translate("stageSelection.acceptStage"), function() {
FlxG.switchState(new StageEditor(creation.name));
});
optionsTree.members[optionsTree.members.length-1].insert(1, option);
@@ -4,8 +4,12 @@ import funkin.editors.stage.elements.StageElementButton;
import funkin.editors.stage.elements.*;
class StageSpritesWindow extends UIButtonList<StageElementButton> {
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("stageSprites." + id, args);
public function new(x:Float, y:Float) {
super(x, y, 400, FlxG.height-25, "Stage Sprites", FlxPoint.get(400, 64));
super(x, y, 400, FlxG.height-25, translate("win-title"), FlxPoint.get(400, 64));
collapsable = true;
topAlpha = 0.9;
@@ -23,22 +27,22 @@ class StageSpritesWindow extends UIButtonList<StageElementButton> {
var screenPos = addButton.getScreenPosition(null, lastDrawCam == null ? FlxG.camera : lastDrawCam);
StageEditor.instance.openContextMenu([
{
label: "Sprite",
label: translate("sprite"),
onSelect: StageEditor.instance._sprite_new,
color: 0xFF00FF00,
icon: 2
},
{
label: "Box",
label: translate("box"),
onSelect: function(_) {
UIState.state.displayNotification(new UIBaseNotification("Creating a box isnt implemented yet!", 2, BOTTOM_LEFT));
UIState.state.displayNotification(new UIBaseNotification(translate("warnings.not-implemented"), 2, BOTTOM_LEFT));
CoolUtil.playMenuSFX(WARNING, 0.45);
},
color: 0xFF00FF00,
icon: 2
},
{
label: "Character",
label: translate("character"),
onSelect: StageEditor.instance._character_new,
color: 0xFF00FF00,
icon: 2
@@ -12,6 +12,9 @@ class StageCharacterButton extends StageElementButton {
public var char:Character;
public var charScale(get, null):Float;
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("stageCharacters." + id, args);
public function new(x:Float,y:Float, char:Character, xml:Access) {
this.char = char;
super(x,y, xml);
@@ -61,9 +64,9 @@ class StageCharacterButton extends StageElementButton {
public override function onDelete() {
if (char.name.startsWith("NO_DELETE_")) {
var text = "This character position is required in stages!";
if (char.name == "NO_DELTE_girlfriend" && FlxG.random.bool(1))
text = "You can't delete Girlfriend";
var text = translate("requiredPosition");
if (char.name == "NO_DELETE_girlfriend" && FlxG.random.bool(1))
text = translate("noDeleteGirlfriend");
UIState.state.displayNotification(new UIBaseNotification(text, 2, BOTTOM_LEFT));
return;
}
@@ -100,6 +103,9 @@ class StageCharacterEditScreen extends UISoftcodedWindow {
public var button:StageCharacterButton;
public var char:Character;
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("stageElementEditScreen." + id, args);
public function new(button:StageCharacterButton) {
this.button = button;
this.char = button.char;
@@ -117,6 +123,7 @@ class StageCharacterEditScreen extends UISoftcodedWindow {
"setEx" => function(name:String, value:Dynamic) {
char.extra.set(StageEditor.exID(name), value);
},
"translate" => translate,
]);
}
@@ -209,8 +209,10 @@ class StageElementButton extends UIButton {
var text = '${getName()} (${CoolUtil.quantize(pos.x, 100)}, ${CoolUtil.quantize(pos.y, 100)})';
var sprite = getSprite();
if(sprite != null) {
text += '\nScale: (${CoolUtil.quantize(sprite.scale.x, 100)}, ${CoolUtil.quantize(sprite.scale.y, 100)})';
text += '\nScroll: (${CoolUtil.quantize(sprite.scrollFactor.x, 100)}, ${CoolUtil.quantize(sprite.scrollFactor.y, 100)})';
var scaleText = TU.getRaw("stageElement.scale");
var scrollText = TU.getRaw("stageElement.scroll");
text += '\n${scaleText.format([CoolUtil.quantize(sprite.scale.x, 100), CoolUtil.quantize(sprite.scale.y, 100)])}';
text += '\n${scrollText.format([CoolUtil.quantize(sprite.scrollFactor.x, 100), CoolUtil.quantize(sprite.scrollFactor.y, 100)])}';
}
pos.put();
return text;
@@ -76,6 +76,9 @@ class StageSpriteEditScreen extends UISoftcodedWindow {
public var sprite:FunkinSprite;
var isSaving:Bool = false;
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("stageElementEditScreen." + id, args);
public function new(button:StageSpriteButton) {
this.button = button;
this.sprite = button.getSprite();
@@ -91,6 +94,7 @@ class StageSpriteEditScreen extends UISoftcodedWindow {
"setEx" => function(name:String, value:Dynamic) {
sprite.extra.set(StageEditor.exID(name), value);
},
"translate" => translate,
]);
}
@@ -75,7 +75,7 @@ class StageUnknownButton extends StageElementButton {
}
public override function getInfoText():String {
return 'Unknown\n<${getName()} />';
return TU.translate("stageElement.unknown", [getName()]);
}
public override function updatePos() {
@@ -86,13 +86,17 @@ class StageUnknownButton extends StageElementButton {
class StageUnknownEditScreen extends UISoftcodedWindow {
public var button:StageUnknownButton;
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("stageElementEditScreen." + id, args);
public function new(button:StageUnknownButton) {
this.button = button;
super("layouts/stage/unknownEditScreen", [
"stage" => StageEditor.instance.stage,
"button" => button,
"xml" => button.xml,
"exID" => StageEditor.exID
"exID" => StageEditor.exID,
"translate" => translate,
]);
}
+17 -17
View File
@@ -54,6 +54,7 @@ class UIAudioPlayer extends UIButton {
timeBarPlayer = new FlxSprite(timeBar.x, timeBar.y).loadGraphic(Paths.image('editors/ui/audio-time-empty'));
timeBarPlayer.colorTransform.color = 0x440364;
timeBarPlayer.scale.y = (timeBarPlayer.frameHeight + 2) / timeBarPlayer.frameHeight;
timeBarPlayer.pixelPerfectRender = true;
members.push(timeBarPlayer);
timeBarSpr = cast new UISprite(timeBar.x, timeBar.y).makeSolid(timeBar.barWidth, timeBar.barHeight, 0x00FFFFFF);
@@ -87,13 +88,13 @@ class UIAudioPlayer extends UIButton {
playingSprite.animation.play(sound.playing ? "playing" : "paused");
timeText.text = '${FlxStringUtil.formatTime(sound.time/1000, true)} / ${FlxStringUtil.formatTime(sound.length/1000)}';
if(timeBarPlayer.clipRect == null)
timeBarPlayer.clipRect = new FlxRect(0, 0, timeBarPlayer.frameWidth, timeBarPlayer.frameHeight);
if(timeBarPlayer.rawClipRect == null)
timeBarPlayer.rawClipRect = new FlxRect(0, 0, timeBarPlayer.frameWidth, timeBarPlayer.frameHeight);
timeBarPlayer.clipRect.x = timeBarPlayer.frameWidth * (sound.time/sound.length);
timeBarPlayer.clipRect.width = 2;
timeBarPlayer.rawClipRect.x = timeBarPlayer.frameWidth * (sound.time/sound.length);
timeBarPlayer.rawClipRect.width = 2;
timeBarPlayer.clipRect = timeBarPlayer.clipRect;
timeBarPlayer.rawClipRect = timeBarPlayer.rawClipRect;
nextPlayerColor = sound.playing ? 0x732D95 : 0x440364;
timeBarPlayer.colorTransform.color = FlxColor.interpolate(timeBarPlayer.colorTransform.color, nextPlayerColor, 1/14);
@@ -106,20 +107,17 @@ class UIAudioPlayer extends UIButton {
var spritePos:FlxPoint = sprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]);
if (((mousePos.x > (spritePos.x)) && (mousePos.x < (spritePos.x) + timeBar.barWidth))
&& ((mousePos.y > (spritePos.y)) && (mousePos.y < (spritePos.y) + timeBar.barHeight))) {
if (FlxG.mouse.justPressed) {
draggingObj = sprite;
wasPlaying = draggingObj == timeBar ? sound.playing : false;
}
if (((mousePos.x > spritePos.x) && (mousePos.x < spritePos.x + sprite.barWidth))
&& ((mousePos.y > spritePos.y) && (mousePos.y < spritePos.y + sprite.barHeight))) {
if (FlxG.mouse.justPressed) {
if ((draggingObj = sprite) == timeBar && (wasPlaying = sound.playing)) sound.pause();
}
if (FlxG.mouse.pressed) {
if (draggingObj == timeBar) {
if (sound.playing) sound.pause();
if (draggingObj == timeBar)
sound.time = FlxMath.remapToRange(mousePos.x - spritePos.x, 0, timeBar.barWidth, 0, sound.length);
} else if (draggingObj == volumeBar) {
else if (draggingObj == volumeBar)
sound.volume = FlxMath.remapToRange(mousePos.x - spritePos.x, 0, volumeBar.barWidth, 0, 1);
}
}
}
spritePos.put();
@@ -127,8 +125,10 @@ class UIAudioPlayer extends UIButton {
mousePos.put();
if (FlxG.mouse.released) {
if (draggingObj == timeBar && wasPlaying)
sound.play(wasPlaying = false, sound.time);
if (draggingObj == timeBar && wasPlaying) {
sound.resume();
wasPlaying = false;
}
draggingObj = null;
}
}
+4 -1
View File
@@ -28,7 +28,10 @@ class UIButton extends UISliceSprite {
public override function onHovered() {
super.onHovered();
if (FlxG.mouse.justPressed) hasBeenPressed = true;
if (FlxG.mouse.justPressed) {
hasBeenPressed = true;
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_BUTTONCLICK_SOUND));
}
if (FlxG.mouse.justReleased && callback != null && shouldPress && hasBeenPressed) {
callback();
hasBeenPressed = false;
@@ -0,0 +1,103 @@
package funkin.editors.ui;
import flixel.util.FlxColor;
import flixel.math.FlxPoint;
import funkin.editors.ui.UIColorwheel.UIColorWheelSelector;
using flixel.util.FlxSpriteUtil;
class UICompactColorwheel extends UISliceSprite {
var colorSlider:FlxSprite;
var colorSliderSelector:UIColorWheelSelector;
var whiteColorButton:UIButton;
public var curColor:Null<FlxColor>;
public var curColorString:String = "#FFFFFF";
public var hue:Float;
public function new (x:Float, y:Float, ?color:Int) {
super(x, y, Std.int((12.5 * 3) + 116), Std.int((12.5 * 2) + (16)), 'editors/ui/inputbox');
curColor = cast color;
hue = curColor.hue;
colorSlider = new FlxSprite(x + 12.5, y + 12.5).makeGraphic(100, 16, FlxColor.TRANSPARENT);
colorSlider.drawRoundRect(0, 0, 100, 16, 10, 10, FlxColor.WHITE);
colorSlider.pixels.lock();
for (pixelx in 0...Std.int(colorSlider.width)) {
var color:Int = FlxColor.fromHSB(pixelx / (colorSlider.width-1) * 360, 1, 1);
for (pixely in 0...Std.int(colorSlider.height))
if (colorSlider.pixels.getPixel32(pixelx, pixely) != FlxColor.TRANSPARENT) colorSlider.pixels.setPixel32(pixelx, pixely, color);
}
colorSlider.pixels.unlock();
whiteColorButton = new UIButton(colorSlider.x + colorSlider.width + 12.5, colorSlider.y, null, () -> {
setColor(FlxColor.WHITE);
}, 16, 16);
whiteColorButton.colorTransform.color = FlxColor.WHITE;
colorSliderSelector = new UIColorWheelSelector(colorSlider.x, colorSlider.y);
colorSlider.antialiasing = true;
for (item in [colorSlider, colorSliderSelector, whiteColorButton])
members.push(item);
updateColor(false);
}
inline function updateColorSliderPickerSelector()
colorSliderSelector.selector.setPosition(colorSlider.x + (hue / 360 * (colorSlider.width-1)) - 8, colorSlider.y);
inline function updateColorSliderMouse(mousePos:FlxPoint)
hue = (mousePos.x / colorSlider.width) * 360;
function setColor(newColor:FlxColor) {
this.color = curColor = colorSliderSelector.curColor = newColor;
}
public function updateColor(checkChanged:Bool = true) {
if(checkChanged) colorChanged = true;
setColor(FlxColor.fromHSB(hue, 1, 1));
updateColorSliderPickerSelector();
curColorString = curColor.toWebString();
}
// For Character Editor
public var colorChanged:Bool = false;
// Make the colorwheel feel better
static inline var hitBoxExtenstion:Float = 8;
var selectedSprite = null;
public override function update(elapsed:Float) {
var mousePos = FlxG.mouse.getScreenPosition(__lastDrawCameras[0], FlxPoint.get());
if (hovered && FlxG.mouse.justPressed) {
for (sprite in [colorSlider]) {
var spritePos:FlxPoint = sprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]);
if (FlxMath.inBounds(mousePos.x, spritePos.x - (hitBoxExtenstion/2), spritePos.x - (hitBoxExtenstion/2) + (sprite.width + hitBoxExtenstion)) && FlxMath.inBounds(mousePos.y, spritePos.y - (hitBoxExtenstion/2), spritePos.y - (hitBoxExtenstion/2) + (sprite.height + hitBoxExtenstion))) {
selectedSprite = sprite;
break;
}
}
}
if (selectedSprite != null) {
var spritePos:FlxPoint = selectedSprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]);
mousePos -= FlxPoint.weak(spritePos.x, spritePos.y);
mousePos.set(CoolUtil.bound(mousePos.x, 0, selectedSprite.width), CoolUtil.bound(mousePos.y, 0, selectedSprite.height));
if (selectedSprite == colorSlider) updateColorSliderMouse(mousePos);
updateColor();
spritePos.put();
if (FlxG.mouse.justReleased) selectedSprite = null;
}
mousePos.put();
super.update(elapsed);
}
}
@@ -88,6 +88,14 @@ class UIContextMenu extends MusicBeatSubstate {
for(o in separators)
o.y -= bg.bHeight;
}
if (bg.x + bg.bWidth > FlxG.width && bg.x > FlxG.width*0.5) {
bg.x -= bg.bWidth;
for(o in contextMenuOptions)
o.x -= bg.bWidth;
for(o in separators)
o.x -= bg.bWidth;
}
}
public function select(option:UIContextMenuOption) {
+1
View File
@@ -87,6 +87,7 @@ class UIDropDown extends UISliceSprite {
}
public function openContextMenu() {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_DROPDOWNAPPEAR_SOUND));
var screenPos = getScreenPosition(null, __lastDrawCameras[0] == null ? FlxG.camera : __lastDrawCameras[0]);
curMenu = UIState.state.openContextMenu([
for(k=>o in items) {
+5 -1
View File
@@ -15,16 +15,19 @@ class UIFileExplorer extends UISliceSprite {
public var onFile:(String, Bytes)->Void;
public var uiElement:FlxSprite;
public var fileType:String = "txt";
public function new(x:Float, y:Float, ?w:Int, ?h:Int, fileType:String = "txt", ?onFile:(String, Bytes)->Void) {
super(x, y, (w != null ? w : 320), (h != null ? h : 58), 'editors/ui/inputbox');
this.fileType = fileType;
if (onFile != null) this.onFile = onFile;
uploadButton = new UIButton(x + 8, y+ 8, null, function () {
var fileDialog = new FileDialog();
fileDialog.onSelect.add(loadFile);
fileDialog.browse(OPEN, fileType);
fileDialog.browse(OPEN, this.fileType);
}, bWidth - 16, bHeight - 16);
members.push(uploadButton);
@@ -66,6 +69,7 @@ class UIFileExplorer extends UISliceSprite {
}
public function removeFile() {
if (!selectable) return;
if (uiElement != null) {
members.remove(uiElement);
uiElement.destroy();
+18 -13
View File
@@ -35,6 +35,9 @@ class UIImageExplorer extends UIFileExplorer {
public var directoryTextBoxLabel:UIText;
public var directoryTextBox:UITextBox;
inline function translate(id:String, ?args:Array<Dynamic>)
return TU.translate("uiImageExplorer." + id, args);
public function new(x:Float, y:Float, image:String, ?w:Int, ?h:Int, ?onFile:(String, Bytes)->Void, ?directory:String = "images") {
super(x, y, w, h, "png, jpg", function (filePath, file) {
if (filePath != null && file != null) uploadImage(filePath, file);
@@ -51,7 +54,7 @@ class UIImageExplorer extends UIFileExplorer {
members.push(directoryIcon);
directoryBG = new UISliceSprite(0, 0, 210+16, 8+12+4+22+8, 'editors/ui/inputbox');
directoryBG.alpha = 0.9; directoryBG.members.push(directoryTextBoxLabel = new UIText(8, 6, 0, 'Directory ($directory/)', 12));
directoryBG.alpha = 0.9; directoryBG.members.push(directoryTextBoxLabel = new UIText(8, 6, 0, TU.getRaw('uiImageExplorer.directory').format([directory]), 12));
directoryTextBox = new UITextBox(8, 8+12+4, "", 210, 22, false, true);
directoryBG.members.push(directoryTextBox);
@@ -245,17 +248,18 @@ class UIImageExplorer extends UIFileExplorer {
message.add('${imagePath.file}.${imagePath.ext}');
message.add(' (${CoolUtil.getSizeString(size)}');
var typeOfFrame = isAtlas ? translate("symbol") : translate("animation");
var shouldUsePlural = animationList.length != 1;
if (animationList.length > 0) {
if(animationList.length == 1) message.add(', ${animationList.length} ${isAtlas ? "Symbol" : "Animation"} Found');
else message.add(', ${animationList.length} ${isAtlas ? "Symbol" : "Animation"}s Found');
} else message.add(', #NO ${isAtlas ? "Symbol" : "Animation"}s Found#');
message.add(', ${TU.getRaw("uiImageExplorer." + (shouldUsePlural ? "foundplural" : "found")).format([animationList.length, typeOfFrame])}');
} else message.add(', ${TU.getRaw("uiImageExplorer.notFound").format([typeOfFrame])}');
if (isAtlas) {
if(spritemaps.length == 1) message.add(', ${spritemaps.length} Spritemap Found');
else message.add(', ${spritemaps.length} Spritemaps Found');
shouldUsePlural = spritemaps.length != 1;
message.add(', ${TU.getRaw("uiImageExplorer.foundAtlas" + (shouldUsePlural ? "plural" : "")).format([spritemaps.length])}');
}
message.add(')');
message.add(isAtlas ? " - Atlas" : " - Spritemap");
message.add(isAtlas ? ' - ${translate("atlas")}' : ' - ${translate("spritemap")}');
fileText = new UIText(x+20, y+16, bWidth-20-deleteButton.bWidth-16, "");
fileText.applyMarkup(message.toString(), [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "#")]);
@@ -337,10 +341,9 @@ class UIImageExplorer extends UIFileExplorer {
}
if (alreadlyExistingFiles.length > 0 && checkExisting) @:privateAccess {
(FlxG.state.subState != null && !FlxG.state._requestSubStateReset ? FlxG.state.subState : FlxG.state).openSubState(new UIWarningSubstate("Alreadly Existing Files!!!",
"The following files alreadly exist: \n\n" + alreadlyExistingFiles.join('\n') + "\n\nIMPORTANT: OVERRIDING CAN NOT BE UNDONE!!!!!!!!",
[ {
label: "Override Files",
var buttons = [
{
label: TU.translate("uiImageExplorer.override"),
color: 0xFFFF0000,
onClick: (_) -> {
deleteExistingFiles();
@@ -348,10 +351,12 @@ class UIImageExplorer extends UIFileExplorer {
}
},
{
label: "Use Existing",
label: TU.translate("uiImageExplorer.useExisting"),
onClick: (_) -> {if (onFinishSaving != null) onFinishSaving();}
}
], false));
];
(FlxG.state.subState != null && !FlxG.state._requestSubStateReset ? FlxG.state.subState : FlxG.state).openSubState(new UIWarningSubstate("Alreadly Existing Files!!!",
TU.getRaw("uiImageExplorer.fileAlreadyExists").format([alreadlyExistingFiles.join('\n')]), buttons, false));
} else acuttalySaveFiles();
}
+1 -1
View File
@@ -4,7 +4,7 @@ import flixel.system.scaleModes.StageSizeScaleMode;
class UIScaleMode extends StageSizeScaleMode {
override public function onMeasure(Width:Int, Height:Int):Void {
if (Width < FlxG.initialWidth || Height < FlxG.initialHeight) {
if ((Width < FlxG.initialWidth || Height < FlxG.initialHeight) && !Options.bypassEditorsResize) {
@:privateAccess {
FlxG.width = FlxG.initialWidth;
FlxG.height = FlxG.initialHeight;
@@ -283,7 +283,7 @@ class UISoftcodedWindow extends UISubstateWindow {
addLayout(getArr(layout.elements));
if(get("hasSaveButton")) {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, "Save & Close", function() {
saveButton = new UIButton(windowSpr.x + windowSpr.bWidth - 20, windowSpr.y + windowSpr.bHeight - 20, TU.translate("editor.saveClose"), function() {
saveData();
close();
}, 125);
@@ -295,7 +295,7 @@ class UISoftcodedWindow extends UISubstateWindow {
if(get("hasCloseButton")) {
var x = saveButton != null ? saveButton.x - 20 : windowSpr.x + windowSpr.bWidth - 20;
var y = saveButton != null ? saveButton.y : windowSpr.y + windowSpr.bHeight - 20;
closeButton = new UIButton(x, y, "Close", function() {
closeButton = new UIButton(x, y, TU.translate("editor.close"), function() {
close();
}, 125);
if(saveButton != null)
+6
View File
@@ -105,6 +105,10 @@ class UIState extends MusicBeatState {
buttonHandler = null;
}
if (FlxG.mouse.justPressed) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_CLICK_SOUND));
}
if (FlxG.mouse.justReleased)
currentFocus = (hoveredSprite is IUIFocusable) ? (cast hoveredSprite) : null;
@@ -143,6 +147,7 @@ class UIState extends MusicBeatState {
}
public function closeCurrentContextMenu() {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND));
if(curContextMenu != null) {
curContextMenu.close();
curContextMenu = null;
@@ -150,6 +155,7 @@ class UIState extends MusicBeatState {
}
public function openContextMenu(options:Array<UIContextMenuOption>, ?callback:UIContextMenuCallback, ?x:Float, ?y:Float, ?w:Int) {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND));
var state = FlxG.state;
while(state.subState != null && !(state._requestSubStateReset && state._requestedSubState == null))
state = state.subState;
@@ -86,6 +86,7 @@ class UISubstateWindow extends MusicBeatSubstate {
}
public override function destroy() {
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWCLOSE_SOUND));
super.destroy();
for(e in camShaders)
e.removeShader(blurShader);
+4 -1
View File
@@ -110,6 +110,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable {
case RIGHT:
changeSelection(1);
case BACKSPACE:
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTREMOVE_SOUND));
if (position > 0) {
label.text = label.text.substr(0, position-1) + label.text.substr(position);
changeSelection(-1);
@@ -119,6 +120,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable {
case END:
position = label.text.length;
case V:
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND));
// Hey lj here, fixed copying because before we checked if the modifier was left or right ctrl
// but somehow it gave a int outside of the KeyModifier's range :sob:
// apparently there is a boolean that just checks for you. yw :D
@@ -129,13 +131,14 @@ class UITextBox extends UISliceSprite implements IUIFocusable {
var data:String = Clipboard.generalClipboard.getData(TEXT_FORMAT);
if (data != null) onTextInput(data);
case C:
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND));
// if we are not holding ctrl, ignore
if (!modifier.ctrlKey) return;
// copying
Clipboard.generalClipboard.setData(TEXT_FORMAT, label.text);
default:
// nothing
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_TEXTTYPE_SOUND));
}
}
@@ -22,6 +22,8 @@ class UIWarningSubstate extends MusicBeatSubstate {
_;
};
public var bHeight:Int = 232;
var title:String;
var message:String;
var buttons:Array<WarningButton>;
@@ -70,14 +72,14 @@ class UIWarningSubstate extends MusicBeatSubstate {
FlxG.cameras.add(warnCam, false);
var spr = new UISliceSprite(0, 0, CoolUtil.maxInt(560, 30 + (170 * buttons.length)), 232, 'editors/ui/${isError ? "normal" : "grayscale"}-popup');
var spr = new UISliceSprite(0, 0, CoolUtil.maxInt(560, 30 + (170 * buttons.length)), bHeight, 'editors/ui/${isError ? "normal" : "grayscale"}-popup');
var sprIcon:FlxSprite = new FlxSprite(spr.x + 18, spr.y + 28 + 26).loadGraphic(Paths.image('editors/warnings/${isError ? "error" : "warning"}'));
sprIcon.scale.set(1.4, 1.4);
sprIcon.updateHitbox();
messageSpr = new UIText(0,0, spr.bWidth - 100 - (26 * 2), message);
spr.bHeight = Std.int(232 + Math.abs(Math.min(sprIcon.height-messageSpr.height, 0)));
spr.bHeight = Std.int(bHeight + Math.abs(Math.min(sprIcon.height-messageSpr.height, 0)));
spr.x = (FlxG.width - spr.bWidth) / 2;
spr.y = (FlxG.height - spr.bHeight) / 2;
@@ -96,7 +98,7 @@ class UIWarningSubstate extends MusicBeatSubstate {
add(sprIcon);
messageSpr.x = sprIcon.x + 70 + 16 + 20;
messageSpr.y = sprIcon.y + 16;
messageSpr.y = sprIcon.y;
add(messageSpr);
var xPos = (FlxG.width - (30 + (170 * buttons.length))) / 2;
+1
View File
@@ -15,6 +15,7 @@ class UIWindow extends UISliceSprite {
content = new FlxTypedGroup<FlxBasic>();
members.push(content);
FlxG.sound.play(Paths.sound(Flags.DEFAULT_EDITOR_WINDOWAPPEAR_SOUND));
}
public override function update(elapsed:Float) {