diff --git a/assets/languages/en/AlphabetEditor.xml b/assets/languages/en/AlphabetEditor.xml new file mode 100644 index 00000000..d3f3d198 --- /dev/null +++ b/assets/languages/en/AlphabetEditor.xml @@ -0,0 +1,62 @@ + + + + + + Press ACCEPT to edit this typeface. + New Typeface + Select a typeface to edit + + + New Typeface: Feature Not Implemented! + This feature isn't implemented yet. Please wait for more cne updates to have this functional.\n\n\n- Codename Devs + + + + + Alphabet Editor + + + File + + New + Save + Save As... + Exit + + + Edit + + Undo + Redo + + + Glyph + + New Glyph + Edit Glyph + Delete Glyph + + + Offsets + + Move Left + Move Up + Move Down + Move Right + Move Extra Left + Move Extra Up + Move Extra Down + Move Extra Right + Clear Offsets + + + View + + Zoom in + Zoom out + Reset Zoom + + + + \ No newline at end of file diff --git a/source/funkin/editors/EditorPicker.hx b/source/funkin/editors/EditorPicker.hx index 3fb4c5d9..c6db792c 100644 --- a/source/funkin/editors/EditorPicker.hx +++ b/source/funkin/editors/EditorPicker.hx @@ -8,6 +8,12 @@ class EditorPicker extends MusicBeatSubstate { // Name is for backwards compatability, dont use it, use id instead public var options:Array = [ + { + name: "Alphabet Editor", + id: "alphabetEditor", + iconID: -1, + state: funkin.editors.alphabet.AlphabetSelection + }, { name: "Chart Editor", id: "charter", @@ -186,8 +192,12 @@ class EditorPickerOption extends FlxTypedSpriteGroup { FlxG.mouse.visible = true; iconSpr = new FlxSprite(); iconSpr.loadGraphic(Paths.image('editors/icons'), true, 128, 128); - iconSpr.animation.add("icon", [iconID], 24, true); - iconSpr.animation.play("icon"); + if(iconID >= 0) { + iconSpr.animation.add("icon", [iconID], 24, true); + iconSpr.animation.play("icon"); + } else { + iconSpr.exists = false; + } iconSpr.antialiasing = true; if (height < 150) { iconSpr.scale.set(height / 150, height / 150); diff --git a/source/funkin/editors/alphabet/AlphabetEditor.hx b/source/funkin/editors/alphabet/AlphabetEditor.hx new file mode 100644 index 00000000..f0b27969 --- /dev/null +++ b/source/funkin/editors/alphabet/AlphabetEditor.hx @@ -0,0 +1,318 @@ +package funkin.editors.alphabet; + +import flixel.math.FlxPoint; +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; + +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; + public var topMenuSpr:UITopMenu; + + public var uiGroup:FlxTypedGroup = new FlxTypedGroup(); + + var editorCamera:FlxCamera; + var uiCamera:FlxCamera; + + public function new(typeface:String) { + super(); + if (typeface != null) __typeface = typeface; + } + + inline function translate(id:String, ?args:Array) + return TU.translate("alphabetEditor." + id, args); + + 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.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: " ", + childs: [ + { + label: "Tape", + //onSelect: _tape + } + ] + }, + { + label: "Move Tape Left", + childs: [ + { + label: "Tape", + //onSelect: _tape + } + ] + }, + { + label: "Move Tape Right", + childs: [ + { + label: "Tape", + //onSelect: _tape + } + ] + } + ]; + + editorCamera = FlxG.camera; + + uiCamera = new FlxCamera(); + uiCamera.bgColor = 0; + + FlxG.cameras.add(uiCamera); + + 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]; + + //var alphabet:Alphabet = new Alphabet(0, 0, __typeface); + //add(alphabet); + + 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; + var alphabetFrames = Paths.getFrames(spritesheet); + + var allChars = ""; + for(frame in alphabetFrames.frames) { + var secondChar = frame.name.charAt(1); + if(secondChar != " ") continue; + if(allChars.contains(frame.name.charAt(0))) continue; + allChars += frame.name.charAt(0); + } + + var letterSprites:Array = []; + for(letter in allChars.split("")) { + var spr = new FlxSprite(0, 0); + spr.frames = alphabetFrames; + spr.antialiasing = true; + spr.animation.addByPrefix("letter", letter, 24); + spr.animation.play("letter"); + add(spr); + letterSprites.push(spr); + } + + var length = letterSprites.length; + var letterWidth = 70; + var letterHeight = 80; + //var width = 10; + //var totalWidth = 10 * 70; + //for(i in 0...length) { + // var letter = letterSprites[i]; + // var x = i % width * 70 + (FlxG.width / 2 - totalWidth / 2); + // var y = Std.int(i / width) * 80 + 100; + // letter.setPosition(x, y); + //} + + var randomIndex = FlxG.random.int(0, length-1); + for(i in 0...length) { + var letter = letterSprites[i]; + letter.alpha = 0.2; + } + letterSprites[randomIndex].alpha = 1; + + for(i in 0...length) { + var letter = letterSprites[i]; + letter.x = i * letterWidth - randomIndex * letterWidth + (FlxG.width / 2 - letterWidth / 2); + letter.y = 50; + } + + var bigletter = new FlxSprite(0, 0); + bigletter.frames = alphabetFrames; + bigletter.antialiasing = true; + bigletter.animation.addByPrefix("letter", allChars.charAt(randomIndex), 24); + bigletter.animation.play("letter"); + bigletter.updateHitbox(); + bigletter.screenCenter(); + bigletter.scale.set(4, 4); + add(bigletter); + + var infoWindow = new GlyphInfoWindow(); + infoWindow.info.text = [ + "Char: " + allChars.charAt(randomIndex), + "Anim Name: " + allChars.charAt(randomIndex) + " bold0", + "X Offset: " + 0, + "Y Offset: " + 0, + "ScaleX: " + 1, + "ScaleY: " + 1, + "Angle: " + 0, + ].join('\n'); + uiGroup.add(infoWindow); + + var leftWindow = new UIWindow(30, 720 - 170 - 30, 230, 170, "Components:"); + leftWindow.members.push({ + var info = new UIText(leftWindow.x + 28, leftWindow.y + 46, 400, ""); + info.text = "[0] Letter (" + allChars.charAt(randomIndex) + ")"; + info.alignment = LEFT; + info; + }); + leftWindow.members.push(new UIButton(leftWindow.x + 28, leftWindow.y + 170 - 40, "Add", function () {})); + uiGroup.add(leftWindow); + + 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 (true) { + if(FlxG.keys.justPressed.ANY) + UIUtil.processShortcuts(topMenu); + } + + if(FlxG.keys.pressed.J) { + editorCamera.scroll.x -= 100 * elapsed; + } + if(FlxG.keys.pressed.L) { + editorCamera.scroll.x += 100 * elapsed; + } + if(FlxG.keys.pressed.I) { + editorCamera.scroll.y -= 100 * elapsed; + } + if(FlxG.keys.pressed.K) { + editorCamera.scroll.y += 100 * elapsed; + } + } + + 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()); + } +} + +class GlyphInfoWindow extends UIWindow { + public var info:UIText; + public function new() { + var width = 230; + var height = 170; + var margin = 30; + super(FlxG.width - width - margin, FlxG.height - height - margin, width, height, "Glyph Info"); + + info = new UIText(x + 28, y + 46, 400, ""); + info.alignment = LEFT; + members.push(info); + } +} \ No newline at end of file diff --git a/source/funkin/editors/alphabet/AlphabetSelection.hx b/source/funkin/editors/alphabet/AlphabetSelection.hx new file mode 100644 index 00000000..87b7412a --- /dev/null +++ b/source/funkin/editors/alphabet/AlphabetSelection.hx @@ -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) + return TU.translate("alphabetSelection." + id, args); + + public override function create() + { + bgType = "charter"; + super.create(); + + var modsList:Array = []; + 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 = [ + 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("alphabetEditor.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); + } +} \ No newline at end of file