From 689eda7114e10f24b2c7fd2bdf0c237536fc6f5c Mon Sep 17 00:00:00 2001 From: SrtHero278 Date: Sun, 10 Aug 2025 11:32:36 -0700 Subject: [PATCH] alphabet editor stuff i still need a way to add spritesheets --- assets/languages/en/Editors.xml | 1 + .../funkin/editors/alphabet/AlphabetEditor.hx | 128 ++++++++++++--- .../editors/alphabet/AlphabetSelection.hx | 7 +- .../editors/alphabet/GlyphInfoWindow.hx | 26 +-- source/funkin/menus/ui/Alphabet.hx | 153 ++++++++++++------ 5 files changed, 227 insertions(+), 88 deletions(-) diff --git a/assets/languages/en/Editors.xml b/assets/languages/en/Editors.xml index be6f9cdb..ae4c27e1 100644 --- a/assets/languages/en/Editors.xml +++ b/assets/languages/en/Editors.xml @@ -488,6 +488,7 @@ New Glyph Edit Glyph Delete Glyph + Delete Current Glyph Offsets diff --git a/source/funkin/editors/alphabet/AlphabetEditor.hx b/source/funkin/editors/alphabet/AlphabetEditor.hx index 3015d7c8..374e56f2 100644 --- a/source/funkin/editors/alphabet/AlphabetEditor.hx +++ b/source/funkin/editors/alphabet/AlphabetEditor.hx @@ -43,6 +43,11 @@ class AlphabetEditor extends UIState { public var queueReorder:Bool = false; public var componentList:UIButtonList; + public var glyphCreateWindow:UISliceSprite; + public var glyphChar:UITextBox; + public var confirmGlyph:UIButton; + public var deleteGlyph:UIButton; + public var infoWindow:GlyphInfoWindow; public var curSelectedComponent:AlphabetComponent = null; public var curSelectedData:AlphabetLetterData = null; @@ -83,13 +88,18 @@ class AlphabetEditor extends UIState { { label: translate("topBar.edit"), childs: [ - { + // TODO: add redo and undo + /*{ label: translate("edit.undo"), - // TODO: add undo + onSelect: null }, { label: translate("edit.redo"), - // TODO: add redo + onSelect: null + },*/ + { + label: translate("glyph.deleteCurGlyph"), + }, { label: "Edit Main Data", // TODO: add translations @@ -97,23 +107,6 @@ class AlphabetEditor extends UIState { } ] }, - { - 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: [ @@ -188,6 +181,67 @@ class AlphabetEditor extends UIState { brokenWarning.color = 0xFFFF6969; add(brokenWarning); + glyphCreateWindow = new UISliceSprite(FlxG.width - 15, topMenuSpr.bHeight + 15, 200, 150, "editors/ui/context-bg"); + glyphCreateWindow.x -= glyphCreateWindow.bWidth; + uiGroup.add(glyphCreateWindow); + + glyphChar = new UITextBox(glyphCreateWindow.x + 15, glyphCreateWindow.y + 15, "", glyphCreateWindow.bWidth - 30); + glyphCreateWindow.members.push(glyphChar); + + confirmGlyph = new UIButton(glyphChar.x, glyphChar.y + glyphChar.bHeight + 15, translate("glyph.newGlyph"), function() { + if (deleteGlyph.selectable) { + curLetter = tape.manualLetters.indexOf(lastChar) + charsForDefault.length; + changeLetter(0); + } else { + tape.manualLetters.push(lastChar); + tape.text = ""; + for (def in charsForDefault) + tape.text += def[Std.int(Math.floor(defaultTmr) % def.length)] + " "; + tape.text += tape.manualLetters.join(" "); + + for (i in 0...tape.fastGetData(lastChar).components.length) { + var anim = bigLetter.text + i; + bigLetter.animation.remove(anim); + tape.animation.remove(anim); + } + + tape.letterData.set(lastChar, { + isDefault: false, + advance: Math.NaN, + advanceEmpty: true, + components: [], + startIndex: 0 + }); + + curLetter = tape.manualLetters.length - 1 + charsForDefault.length; + changeLetter(0); + } + }, glyphChar.bWidth); + confirmGlyph.selectable = false; + glyphCreateWindow.members.push(confirmGlyph); + + deleteGlyph = new UIButton(glyphChar.x, confirmGlyph.y + confirmGlyph.bHeight + 5, translate("glyph.deleteGlyph"), function() { + final charIdx = tape.manualLetters.indexOf(lastChar); + + tape.manualLetters.splice(charIdx, 1); + tape.text = ""; + for (def in charsForDefault) + tape.text += def[Std.int(Math.floor(defaultTmr) % def.length)] + " "; + tape.text += tape.manualLetters.join(" "); + + for (i in 0...tape.fastGetData(lastChar).components.length) { + var anim = bigLetter.text + i; + bigLetter.animation.remove(anim); + tape.animation.remove(anim); + } + tape.letterData.remove(lastChar); + + changeLetter((curLetter >= charIdx + charsForDefault.length) ? -1 : 0); + }, glyphChar.bWidth); + deleteGlyph.color = FlxColor.RED; + deleteGlyph.selectable = false; + glyphCreateWindow.members.push(deleteGlyph); + infoWindow = new GlyphInfoWindow(); uiGroup.add(infoWindow); @@ -208,7 +262,7 @@ class AlphabetEditor extends UIState { scaleX: 1, scaleY: 1, - + flipX: false, flipY: false, @@ -247,8 +301,29 @@ class AlphabetEditor extends UIState { } } + var lastChar:String = ""; public override function update(elapsed:Float) { super.update(elapsed); + + if (glyphChar.label.text != lastChar) { + if (glyphChar.label.text == "") { + lastChar = ""; + confirmGlyph.selectable = false; + deleteGlyph.selectable = false; + confirmGlyph.field.text = translate("glyph.newGlyph"); + } else { + glyphChar.label.text = lastChar = switch (tape.forceCase) { + case UPPER: glyphChar.label.text.charAt(glyphChar.label.text.length - 1).toUpperCase(); + case LOWER: glyphChar.label.text.charAt(glyphChar.label.text.length - 1).toLowerCase(); + case NONE: glyphChar.label.text.charAt(glyphChar.label.text.length - 1); + }; + + confirmGlyph.selectable = true; + deleteGlyph.selectable = tape.manualLetters.contains(lastChar); + confirmGlyph.field.text = translate(deleteGlyph.selectable ? "glyph.editGlyph" : "glyph.newGlyph"); + } + } + if (queueReorder) { queueReorder = false; var outlines = []; @@ -344,6 +419,7 @@ class AlphabetEditor extends UIState { 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]; + glyphChar.label.text = bigLetter.text; bigLetter.updateHitbox(); bigLetter.screenCenter(); @@ -396,11 +472,17 @@ class AlphabetEditor extends UIState { changeLetter(1); } + function buildAlphabet() { + var tempPrettyPrint = true; + var xmlThingYea:String = "\n" + Printer.print(tape.buildXML(), tempPrettyPrint); + return tempPrettyPrint ? xmlThingYea : xmlThingYea.replace("\n", ""); + } + function _file_save(_) { #if sys CoolUtil.safeSaveFile( '${Paths.getAssetsRoot()}/data/alphabet/${__typeface}.xml', - ""//alphabet.buildXML() + buildAlphabet() ); #else _file_saveas(_); @@ -408,7 +490,7 @@ class AlphabetEditor extends UIState { } function _file_saveas(_) { - openSubState(new SaveSubstate(""/*alphabet.buildXML()*/, { + openSubState(new SaveSubstate(buildAlphabet(), { defaultSaveFile: '${__typeface}.xml' })); } diff --git a/source/funkin/editors/alphabet/AlphabetSelection.hx b/source/funkin/editors/alphabet/AlphabetSelection.hx index bb274c8f..c11d0856 100644 --- a/source/funkin/editors/alphabet/AlphabetSelection.hx +++ b/source/funkin/editors/alphabet/AlphabetSelection.hx @@ -43,14 +43,11 @@ class AlphabetIconOption extends TextOption { var spritesheet = null; for (node in xml.elements()) { if (node.nodeName == "spritesheet") { - spritesheet = node.firstChild().nodeValue; + spritesheet = node.firstChild().nodeValue.trim(); break; } } - var useColorOffsets = xml.get("useColorOffsets").getDefault("false") == "true"; - - // todo fix crash if invalid spritesheet; iconSpr = new FlxSprite(); @@ -64,7 +61,7 @@ class AlphabetIconOption extends TextOption { } } iconSpr.frame = frameToUse; - if (useColorOffsets) { + if (xml.get("colorMode") == "offsets") { iconSpr.colorTransform.color = -1; } iconSpr.setPosition(90 - iconSpr.width - 20, (__text.height - iconSpr.height) / 2); diff --git a/source/funkin/editors/alphabet/GlyphInfoWindow.hx b/source/funkin/editors/alphabet/GlyphInfoWindow.hx index 02326321..f6018ea8 100644 --- a/source/funkin/editors/alphabet/GlyphInfoWindow.hx +++ b/source/funkin/editors/alphabet/GlyphInfoWindow.hx @@ -10,7 +10,7 @@ class GlyphInfoWindow extends UIWindow { public var scaleYBox:UINumericStepper; public var angleBox:UINumericStepper; public var colorModeDrop:UIDropDown; - + public var flipXBox:UICheckbox; public var flipYBox:UICheckbox; @@ -60,7 +60,7 @@ class GlyphInfoWindow extends UIWindow { 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; + compon.x = -val; }); members.push(xBox); @@ -121,7 +121,7 @@ class GlyphInfoWindow extends UIWindow { members.push(colorModeDrop); addLabelOn(colorModeDrop, "Color Mode"); - outlineCheck = new UICheckbox(colorModeDrop.x + colorModeDrop.bWidth + itemMargin * 2, colorModeDrop.y + 4, "Use Outline?"); + outlineCheck = new UICheckbox(colorModeDrop.x + colorModeDrop.bWidth + itemMargin * 2, colorModeDrop.y + 10, "Use Outline?"); outlineCheck.onChecked = function(val) { targetPercent = val ? 1 : 0; @@ -147,7 +147,7 @@ class GlyphInfoWindow extends UIWindow { angle: compon.angle, cos: compon.cos, sin: compon.sin, - + flipX: compon.flipX, flipY: compon.flipY, @@ -156,15 +156,17 @@ class GlyphInfoWindow extends UIWindow { }; data.components.insert(AlphabetEditor.instance.outlineIdx, newOutline); + ++AlphabetEditor.instance.outlineIdx; ++data.startIndex; } else { compon.outIndex = null; data.components.splice(AlphabetEditor.instance.outlineIdx - 1, 1); + --AlphabetEditor.instance.outlineIdx; --data.startIndex; } } members.push(outlineCheck); - + flipXBox = new UICheckbox(outlineCheck.x, outlineCheck.y - outlineCheck.height - 8, "Flip X?"); flipXBox.onChecked = function(check:Bool) { compon.flipX = check; @@ -254,7 +256,7 @@ class GlyphInfoWindow extends UIWindow { } prefixBox.label.text = com.anim; - xBox.value = com.x; + xBox.value = -com.x; yBox.value = com.y; scaleXBox.value = com.scaleX; scaleYBox.value = com.scaleY; @@ -276,12 +278,12 @@ class GlyphInfoWindow extends UIWindow { outlineYBox.value = out.y; targetPercent = 1; } - - for (item in members) { - if (item is UISprite) - cast(item, UISprite).selectable = true; - } - colorModeDrop.dropButton.selectable = true; + + for (item in [prefixBox, scaleXBox, scaleYBox, angleBox, flipXBox, flipYBox]) + item.selectable = true; + for (item in [xBox, yBox, colorModeDrop, outlineCheck, outlineBox, outlineXBox, outlineYBox]) + item.selectable = !data.isDefault; + colorModeDrop.dropButton.selectable = colorModeDrop.selectable; } function valueSet(item:UINumericStepper, func:Dynamic, text:String) { diff --git a/source/funkin/menus/ui/Alphabet.hx b/source/funkin/menus/ui/Alphabet.hx index fd920642..de64e347 100644 --- a/source/funkin/menus/ui/Alphabet.hx +++ b/source/funkin/menus/ui/Alphabet.hx @@ -36,7 +36,7 @@ final class AlphabetComponent { public var cos:Float; public var scaleX:Float; public var scaleY:Float; - + public var flipX:Bool; public var flipY:Bool; @@ -163,7 +163,7 @@ class Alphabet extends FlxSprite { }; var manualLetters:Array = []; var failedLetters:Array = []; - var failedOutlines:Array = []; + var sheets:Array = []; public var renderMode:AlphabetRenderMode = DEFAULT; @@ -321,7 +321,7 @@ class Alphabet extends FlxSprite { if (!isOnScreen(camera)) { frameOffset.y -= offsetY; frameOffset.x -= offsetX; - break; + continue; } if (__component.hasColorMode) @@ -436,10 +436,8 @@ class Alphabet extends FlxSprite { if (anim == null) return defaultAdvance; - if (data.advanceEmpty && !data.isDefault) { - data.advanceEmpty = false; + if (data.advanceEmpty && !data.isDefault) data.advance = frames.frames[anim.frames[0]].sourceSize.x; - } return (data.isDefault) ? frames.frames[anim.frames[0]].sourceSize.x : data.advance; } @@ -514,28 +512,17 @@ class Alphabet extends FlxSprite { return animation.getByName(name); } - /*function getLetterOutline(char:String, component:AlphabetComponent, index:Int) { - var name = char + "Outline" + Std.string(index); - if (failedOutlines.contains(name)) return null; - if (animation.exists(name)) return animation.getByName(name); - - animation.addByPrefix(name, component.outline.anim, fps); - if (!animation.exists(name)) { - failedOutlines.push(name); - return null; - } - return animation.getByName(name); - }*/ - function checkNode(node:Xml):Void { switch (node.nodeName) { case "spritesheet": + final sheet = node.firstChild().nodeValue.trim(); if (frames == null) - frames = Paths.getFrames(node.firstChild().nodeValue); + frames = Paths.getFrames(sheet); else { - for (frame in Paths.getFrames(node.firstChild().nodeValue).frames) + for (frame in Paths.getFrames(sheet).frames) frames.pushFrame(frame); } + sheets.push(sheet); case "defaultAnim": var idx = ["UPPER", "LOWER"].indexOf(node.get("casing").toUpperCase()) + 1; @@ -549,7 +536,7 @@ class Alphabet extends FlxSprite { advance: 0.0, advanceEmpty: true, components: [{ - anim: node.firstChild().nodeValue, + anim: node.firstChild().nodeValue.trim(), x: 0.0, y: 0.0, @@ -560,7 +547,7 @@ class Alphabet extends FlxSprite { angle: angle, cos: angleCos, sin: angleSin, - + flipX: node.get("flipX") == "true", flipY: node.get("flipY") == "true", @@ -616,7 +603,7 @@ class Alphabet extends FlxSprite { angle: angle, cos: angleCos, sin: angleSin, - + flipX: xFlip, flipY: yFlip, @@ -639,7 +626,7 @@ class Alphabet extends FlxSprite { angle: angle, cos: angleCos, sin: angleSin, - + flipX: xFlip, flipY: yFlip, @@ -673,7 +660,7 @@ class Alphabet extends FlxSprite { var xScale:Float = Std.parseFloat(node.get("scaleX")).getDefaultFloat(1.0); var yScale:Float = Std.parseFloat(node.get("scaleY")).getDefaultFloat(1.0); var advance:Float = Std.parseFloat(node.get("advance")); - + var xFlip = node.get("flipX") == "true"; var yFlip = node.get("flipY") == "true"; @@ -694,7 +681,7 @@ class Alphabet extends FlxSprite { angle: angle, cos: angleCos, sin: angleSin, - + flipX: xFlip, flipY: yFlip, @@ -705,7 +692,7 @@ class Alphabet extends FlxSprite { components.push({ outIndex: (node.get("hasOutline") == "true") ? 0 : null, - anim: node.firstChild().nodeValue, + anim: node.firstChild().nodeValue.trim(), x: xOff, y: yOff, @@ -766,6 +753,8 @@ class Alphabet extends FlxSprite { v[2] = []; v; } + sheets = []; + manualLetters = []; failedLetters = [" "]; defaultAdvance = Std.parseFloat(xml.get("advance")).getDefaultFloat(40.0); @@ -781,34 +770,102 @@ class Alphabet extends FlxSprite { checkNode(node); } - private static var alphabetProperties:Array = ["fps", "advance", "lineGap", "forceCasing", "useColorOffsets", "antialiasing"]; - private static var componentProperties:Array = ["name", "anim", "x", "y", "scaleX", "scaleY", "angle", "offsetX", "offsetY", "flipX", "flipY"]; + private static var alphabetProperties:Array = ["fps", "advance", "lineGap", "forceCasing", "colorMode", "antialiasing"]; + private static var componentProperties:Array = ["char", "advance", "anim", "x", "y", "scaleX", "scaleY", "flipX", "flipY", "angle", "colorMode", "hasOutline", "outline", "outlineX", "outlineY"]; public function buildXML():Xml { - var xml = Xml.createElement("alphabet"); + var xml = Xml.createElement("alphabetFont"); xml.set("fps", Std.string(fps)); xml.set("advance", Std.string(defaultAdvance)); xml.set("lineGap", Std.string(lineGap)); xml.set("forceCasing", ["none", "upper", "lower"][forceCase]); - xml.set("useColorOffsets", ["tint", "offsets", "none"][colorMode]); + xml.set("colorMode", ["tint", "offsets", "none"][colorMode]); xml.set("antialiasing", antialiasing ? "true" : "false"); xml.attributeOrder = alphabetProperties; - /*for (component in components) { - var componentXml = Xml.createElement("component"); - componentXml.attributeOrder = componentProperties; - componentXml.set("name", component.name); - componentXml.set("anim", component.anim); - componentXml.set("x", Std.string(component.x)); - componentXml.set("y", Std.string(component.y)); - if(component.scaleX != 1) componentXml.set("scaleX", Std.string(component.scaleX)); - if(component.scaleY != 1) componentXml.set("scaleY", Std.string(component.scaleY)); - if(component.angle != 0) componentXml.set("angle", Std.string(component.angle)); - if(component.offsetX != 0) componentXml.set("offsetX", Std.string(component.offsetX)); - if(component.offsetY != 0) componentXml.set("offsetY", Std.string(component.offsetY)); - xml.addChild(componentXml); - }*/ + for (sheet in sheets) { + var sheetNode = Xml.createElement("spritesheet"); + sheetNode.addChild(Xml.createPCData(sheet)); + xml.addChild(sheetNode); + } + + for (i in 0...3) { + if (defaults[i] == null) continue; + + var compon = defaults[i].components[0]; + var node = Xml.createElement("defaultAnim"); + node.addChild(Xml.createPCData(compon.anim)); + if (i != 0) + node.set("casing", ["upper", "lower"][i - 1]); + + if (compon.scaleX != 1.0 || compon.scaleY != 1.0) { + node.set("scaleX", Std.string(compon.scaleX)); + node.set("scaleY", Std.string(compon.scaleY)); + } + if (compon.flipX || compon.flipY) { + node.set("flipX", Std.string(compon.flipX)); + node.set("flipY", Std.string(compon.flipY)); + } + if (compon.angle != 0.0) + node.set("angle", Std.string(compon.angle)); + + node.attributeOrder = componentProperties; + xml.addChild(node); + } + + for (let in manualLetters) { + var data = fastGetData(let); + var node = Xml.createElement(data.components.length - data.startIndex > 1 ? "composite" : "anim"); + node.set("char", let); + if (!data.advanceEmpty) + node.set("advance", Std.string(data.advance)); + + for (i in data.startIndex...data.components.length) { + var compon = data.components[i]; + var cNode:Xml = node; + + if (data.components.length - data.startIndex > 1) { + cNode = Xml.createElement("component"); + cNode.set("anim", compon.anim); + node.addChild(cNode); + } else + cNode.addChild(Xml.createPCData(compon.anim)); + + if (compon.x != 0.0 || compon.y != 0.0) { + cNode.set("x", Std.string(-compon.x)); + cNode.set("y", Std.string(compon.y)); + } + if (compon.scaleX != 1.0 || compon.scaleY != 1.0) { + cNode.set("scaleX", Std.string(compon.scaleX)); + cNode.set("scaleY", Std.string(compon.scaleY)); + } + if (compon.flipX || compon.flipY) { + cNode.set("flipX", Std.string(compon.flipX)); + cNode.set("flipY", Std.string(compon.flipY)); + } + if (compon.angle != 0.0) + cNode.set("angle", Std.string(compon.angle)); + if (compon.hasColorMode) + cNode.set("colorMode", ["tint", "offsets", "none"][compon.colorMode]); + + if (compon.outIndex != null) { + var outline = data.components[compon.outIndex]; + + cNode.set("hasOutline", "true"); + cNode.set("outline", outline.anim); + if (outline.x != 0.0 || outline.y != 0.0) { + cNode.set("outlineX", Std.string(outline.x)); + cNode.set("outlineY", Std.string(outline.y)); + } + } + + cNode.attributeOrder = componentProperties; + } + + node.attributeOrder = componentProperties; + xml.addChild(node); + } return xml; } @@ -828,9 +885,9 @@ class Alphabet extends FlxSprite { this.letterData = from.letterData; this.defaults = from.defaults; this.loaded = from.loaded; + this.sheets = from.sheets; this.manualLetters = from.manualLetters; this.failedLetters = from.failedLetters; - this.failedOutlines = from.failedOutlines; } override function destroy():Void { @@ -843,7 +900,7 @@ class Alphabet extends FlxSprite { defaults = null; manualLetters = null; failedLetters = null; - failedOutlines = null; + sheets = null; super.destroy(); }