Work on alphabet editor

This commit is contained in:
Ne_Eo
2025-01-19 18:14:17 +01:00
parent 63cd0b6dba
commit f6891dda52
5 changed files with 376 additions and 82 deletions
+58 -27
View File
@@ -1,34 +1,66 @@
<!DOCTYPE codename-engine-alphabet-font>
<alphabetFont fps="24" advance="30" lineGap="70" colorMode="offsets">
<alphabetFont fps="12" advance="30" lineGap="70" colorMode="offsets">
<spritesheet>menus/alphabet</spritesheet>
<defaultAnim casing="upper">LETTER capital</defaultAnim>
<defaultAnim casing="lower">LETTER lowercase</defaultAnim>
<defaultAnim casing="upper">character-LOWERLETTER-capital</defaultAnim>
<defaultAnim casing="lower">character-LOWERLETTER-lowercase</defaultAnim>
<defaultAnim>LETTER</defaultAnim>
<anim char="g" y="-15">g lowercase</anim>
<anim char="j" y="-15">j lowercase</anim>
<anim char="p" y="-15">p lowercase</anim>
<anim char="q" y="-15">q lowercase</anim>
<anim char="y" y="-15">y lowercase</anim>
<anim char="→" y="9">right arrow</anim>
<anim char="←" y="9">left arrow</anim>
<anim char="↓" y="5">down arrow</anim>
<anim char="↑" y="4">up arrow</anim>
<anim char="`" y="32" scaleY="0.5">comma</anim>
<anim char="," y="-9">comma</anim>
<anim char="'" y="28">comma</anim>
<anim char=".">period</anim>
<anim char="!">exclamation point</anim>
<anim char="?">question mark</anim>
<anim char="/">forward slash</anim>
<anim char="&#34;" y="22">start parentheses</anim>
<anim char="-" y="15">-</anim>
<anim char="^" y="28">^</anim>
<anim char="~" y="17">~</anim>
<anim char="=" y="13">=</anim>
<anim char="g" y="-15">character-g-lowercase</anim>
<anim char="j" y="-15">character-j-lowercase</anim>
<anim char="p" y="-15">character-p-lowercase</anim>
<anim char="q" y="-15">character-q-lowercase</anim>
<anim char="y" y="-15">character-y-lowercase</anim>
<composite char="á">
<anim char="0">character-zero</anim>
<anim char="1">character-one</anim>
<anim char="2">character-two</anim>
<anim char="3">character-three</anim>
<anim char="4">character-four</anim>
<anim char="5">character-five</anim>
<anim char="6">character-six</anim>
<anim char="7">character-seven</anim>
<anim char="8">character-eight</anim>
<anim char="9">character-nine</anim>
<anim char="&amp;">character-ampersand</anim>
<anim char="&lt;">character-anglebracket-left</anim>
<anim char="&gt;">character-anglebracket-right</anim>
<anim char="→" y="9">character-arrow-right</anim>
<anim char="←" y="9">character-arrow-left</anim>
<anim char="↓" y="5">character-arrow-down</anim>
<anim char="↑" y="4">character-arrow-up</anim>
<anim char="*" y="30">character-asterisk</anim>
<anim char="@">character-at</anim>
<anim char="\">character-backslash</anim>
<anim char="`" y="30">character-backtick</anim>
<anim char="^" y="28">character-caret</anim>
<anim char=":">character-colon</anim>
<anim char="," y="-9">character-comma</anim>
<anim char="{">character-curlybracket-left</anim>
<anim char="}">character-curlybracket-right</anim>
<anim char="$">character-dollar</anim>
<anim char='"' y="28">character-doublequote-start</anim>
<anim char="=" y="13">character-equal</anim>
<anim char="!">character-exclamationmark</anim>
<anim char="-" y="20">character-minus</anim>
<anim char="%">character-percent</anim>
<anim char=".">character-period</anim>
<anim char="+" y="9">character-plus</anim>
<anim char="#" y="5">character-pound</anim>
<anim char="?">character-questionmark</anim>
<anim char="(">character-roundbracket-left</anim>
<anim char=")">character-roundbracket-right</anim>
<anim char=";" y="-9">character-semicolon</anim>
<anim char="'" y="28">character-singlequote</anim>
<anim char="/">character-slash</anim>
<anim char="[">character-squarebracket-left</anim>
<anim char="]">character-squarebracket-right</anim>
<anim char="~" y="17">character-tilde</anim>
<anim char="_">character-underscore</anim>
<anim char="|">character-verticalbar</anim>
<!-- <composite char="á">
<component anim="a lowercase0" x="0" y="0"/>
<component anim="comma" scaleY="0.5" x="7" y="35"/>
</composite>
@@ -56,7 +88,6 @@
<composite char="ą">
<component anim="a lowercase0" x="0" y="0"/>
<!-- temp character for now :sob: -->
<component anim="question hook0" scaleX="0.5" scaleY="0.5" x="10" y="-25" angle="200"/>
</composite>
@@ -83,5 +114,5 @@
<component anim="o lowercase0" x="0" y="0"/>
<component anim="period" x="0" y="35"/>
<component anim="period" x="20" y="35"/>
</composite>
</composite> -->
</alphabetFont>
+1 -1
View File
@@ -69,7 +69,7 @@ class MainState extends FlxState {
if(Framerate.isLoaded)
Framerate.instance.reload();
FlxG.switchState(new funkin.editors.alphabet.AlphabetEditor("bold"));
FlxG.switchState(new funkin.editors.alphabet.AlphabetEditor("normal"));
/*
if (Flags.DISABLE_BETA_WARNING_SCREEN || betaWarningShown)
FlxG.switchState(new TitleState());
@@ -35,7 +35,7 @@ class AlphabetEditor extends UIState {
return TU.translate("editor.alphabet." + id, args);
public var tape:Alphabet;
public var bigletter:Alphabet;
public var bigLetter:Alphabet;
public var curLetter:Int = 0;
public var targetX:Float = 0;
@@ -43,7 +43,8 @@ class AlphabetEditor extends UIState {
public var curSelectedComponent:AlphabetComponent = null;
public var allChars:Array<String> = [];
public var defaultTmr:Float = 0.0;
public var charsForDefault:Array<String> = [];
public override function create() {
super.create();
@@ -74,6 +75,23 @@ class AlphabetEditor extends UIState {
]
// 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: [
@@ -143,51 +161,23 @@ class AlphabetEditor extends UIState {
topMenuSpr = new UITopMenu(topMenu);
topMenuSpr.cameras = uiGroup.cameras = [uiCamera];
//var alphabet:Alphabet = new Alphabet(0, 0, __typeface);
//add(alphabet);
allChars = [];
var helperAlphabet = new Alphabet(0, 0, "meow", __typeface);
for(cc in 33...0xffff) {
// todo: unicode
var letter = String.fromCharCode(cc);
var data = helperAlphabet.fastGetData(letter);
if(data == null) continue;
if(data.components.length == 0) continue;
var hasAnim = true;
var component;
for (i in 0...data.components.length) {
component = data.components[i];
var anim = helperAlphabet.fastGetLetterAnim(letter, data, component, i);
if(anim == null) {
hasAnim = false;
break;
}
}
if(!hasAnim) continue;
allChars.push(letter);
}
// todo get other characters
trace(allChars.join(" "));
tape = new Alphabet(0, 70, allChars.join(" "), __typeface);
tape = new Alphabet(0, 70, "", __typeface);
tape.alignment = CENTER;
tape.renderMode = MONOSPACE;
tape.x = targetX = FlxG.width * 0.5 - tape.defaultAdvance * 0.5;
add(tape);
bigletter = new Alphabet(0, 0, "A", __typeface);
bigletter.alignment = CENTER;
bigletter.scale.set(4, 4);
bigLetter = new Alphabet(0, 0, charsForDefault[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);
bigLetter.updateHitbox();
bigLetter.screenCenter();
add(bigLetter);
updateTape();
tape.x = targetX;
var infoWindow = new GlyphInfoWindow();
infoWindow.info.text = [
@@ -253,15 +243,37 @@ class AlphabetEditor extends UIState {
}
}
var lastIdx = Math.floor(defaultTmr);
defaultTmr += elapsed * 0.5;
if (lastIdx != (lastIdx = Math.floor(defaultTmr)))
tape.text = charsForDefault[Math.floor(defaultTmr)] + " " + 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;
}
charsForDefault.push(letter);
}
defaultTmr %= charsForDefault.length;
tape.text = charsForDefault[Math.floor(defaultTmr)] + " " + tape.manualLetters.join(" ");
changeLetter(0);
}
function changeLetter(inc:Int) {
curLetter = CoolUtil.positiveModuloInt(curLetter + inc, allChars.length);
curLetter = CoolUtil.positiveModuloInt(curLetter + inc, tape.manualLetters.length + 1);
targetX = FlxG.width * 0.5 - tape.defaultAdvance * (0.5 + curLetter * 2);
bigletter.text = allChars[curLetter];
bigletter.updateHitbox();
bigletter.screenCenter();
bigLetter.text = (curLetter == 0) ? charsForDefault[0] : tape.manualLetters[curLetter - 1];
bigLetter.updateHitbox();
bigLetter.screenCenter();
}
function _tape_left(_) {
@@ -292,6 +304,10 @@ class AlphabetEditor extends UIState {
/*if (undos.unsaved) SaveWarning.triggerWarning();
else */FlxG.switchState(new AlphabetSelection());
}
function _edit_main(_) {
FlxG.state.openSubState(new AlphabetMainDataScreen());
}
}
/*
@@ -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;
}
}
+40 -7
View File
@@ -114,6 +114,7 @@ enum abstract AlphabetRenderMode(ByteUInt) from ByteUInt to ByteUInt {
}
@:allow(funkin.editors.alphabet.AlphabetEditor)
@:allow(funkin.editors.alphabet.AlphabetMainDataScreen)
class Alphabet extends FlxSprite {
public var effects:Array<RegionEffect> = [];
var __renderData:AlphabetRenderData;
@@ -128,7 +129,7 @@ class Alphabet extends FlxSprite {
public var alignment:AlphabetAlignment = LEFT;
public var forceCase:CaseMode = NONE;
var __laneWidths:Array<Float> = []; // TODO: add alignment.
var __laneWidths:Array<Float> = [];
var __forceWidth:Float = 0.0; // TODO: implement functionality for this.
var __queueResize:Bool = false;
var __animTime:Float = 0.0;
@@ -156,6 +157,7 @@ class Alphabet extends FlxSprite {
v[2] = [];
v;
};
var manualLetters:Array<String> = [];
var failedLetters:Array<String> = [];
var failedOutlines:Array<String> = [];
@@ -168,7 +170,8 @@ class Alphabet extends FlxSprite {
public function new(?x:Float, ?y:Float, ?text:String = "", ?font:OneOfTwo<String, Bool> = "normal") {
super(x, y);
this.text = text;
this.font = (font is String) ? font : ((font == true) ? "bold" : "normal"); // == true just in case
if (font != "<SKIP>")
this.font = (font is String) ? font : ((font == true) ? "bold" : "normal"); // == true just in case
this.__renderData = new AlphabetRenderData(this);
}
@@ -482,7 +485,7 @@ class Alphabet extends FlxSprite {
var name = char + Std.string(index);
var anim = (data.isDefault) ?
component.anim.replace("LETTER", char) :
component.anim.replace("LOWERLETTER", char.toLowerCase()).replace("UPPERLETTER", char.toUpperCase()).replace("LETTER", char) :
component.anim;
animation.addByPrefix(name, anim, fps);
return animation.exists(name) ? animation.getByName(name) : null;
@@ -494,7 +497,7 @@ class Alphabet extends FlxSprite {
if (animation.exists(name)) return animation.getByName(name);
var anim = (data.isDefault) ?
component.anim.replace("LETTER", char) :
component.anim.replace("LOWERLETTER", char.toLowerCase()).replace("UPPERLETTER", char.toUpperCase()).replace("LETTER", char) :
component.anim;
animation.addByPrefix(name, anim, fps);
if (!animation.exists(name)) {
@@ -634,6 +637,7 @@ class Alphabet extends FlxSprite {
components: components,
startIndex: startIndex
});
manualLetters.push(char);
case "anim":
if(!node.exists("char")) {
Logs.error("<anim> must have a char attribute", "Alphabet");
@@ -698,6 +702,7 @@ class Alphabet extends FlxSprite {
components: components,
startIndex: (node.get("hasOutline") == "true") ? 1 : 0
});
manualLetters.push(char);
case "languageSection": // used to only parse characters if a specific language is selected
if(!node.exists("langs")) {
Logs.error("<languageSection> must have a langs attribute", "Alphabet");
@@ -756,8 +761,8 @@ class Alphabet extends FlxSprite {
xml.set("fps", Std.string(fps));
xml.set("advance", Std.string(defaultAdvance));
xml.set("lineGap", Std.string(lineGap));
//xml.set("forceCasing", forceCase);
xml.set("useColorOffsets", colorMode == OFFSET ? "true" : "false");
xml.set("forceCasing", ["none", "upper", "lower"][forceCase]);
xml.set("useColorOffsets", ["tint", "offsets", "none"][colorMode]);
xml.set("antialiasing", antialiasing ? "true" : "false");
xml.attributeOrder = alphabetProperties;
@@ -780,9 +785,37 @@ class Alphabet extends FlxSprite {
return xml;
}
public function copyData(from:Alphabet) {
if (from.font == this.font) return;
__queueResize = true;
@:bypassAccessor @:bypassAccessor this.font = from.font; // double bypass in case haxe gets finicky
this.frames = from.frames;
this.antialiasing = from.antialiasing;
this.fps = from.fps;
this.defaultAdvance = from.defaultAdvance;
this.lineGap = from.lineGap;
this.forceCase = from.forceCase;
this.colorMode = from.colorMode;
this.letterData = from.letterData;
this.defaults = from.defaults;
this.loaded = from.loaded;
this.manualLetters = from.manualLetters;
this.failedLetters = from.failedLetters;
this.failedOutlines = from.failedOutlines;
}
override function destroy():Void {
originOffset = FlxDestroyUtil.destroy(originOffset);
failedLetters = [];
__drawScale = FlxDestroyUtil.put(__drawScale);
__renderData = null;
__laneWidths = null;
effects = null;
letterData = null;
defaults = null;
manualLetters = null;
failedLetters = null;
failedOutlines = null;
super.destroy();
}