Files
Funkin/source/funkin/ui/AtlasText.hx
T

317 lines
7.4 KiB
Haxe

package funkin.ui;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxAtlasFrames;
import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
import flixel.util.FlxStringUtil;
/**
* AtlasText is an improved version of Alphabet and FlxBitmapText.
* It supports animations on the letters, and is less buggy than Alphabet.
*/
@:nullSafety
class AtlasText extends FlxTypedSpriteGroup<AtlasChar>
{
static var fonts:Map<AtlasFont, AtlasFontData> = new Map<AtlasFont, AtlasFontData>();
static var casesAllowed:Map<AtlasFont, Case> = new Map<AtlasFont, Case>();
public var text(default, set):String = '';
var font:AtlasFontData = new AtlasFontData(AtlasFont.DEFAULT);
public var atlas(get, never):FlxAtlasFrames;
inline function get_atlas():FlxAtlasFrames return font.atlas;
public var caseAllowed(get, never):Case;
inline function get_caseAllowed():Case return font.caseAllowed;
public var maxHeight(get, never):Float;
inline function get_maxHeight():Float return font.maxHeight;
public function new(x = 0.0, y = 0.0, text:String = '', fontName:AtlasFont = AtlasFont.DEFAULT)
{
if (!fonts.exists(fontName)) fonts[fontName] = new AtlasFontData(fontName);
font = fonts[fontName] ?? new AtlasFontData(fontName);
super(x, y);
this.text = text;
}
function set_text(value:String):String
{
value ??= '';
var caseValue:String = restrictCase(value);
var caseText:String = restrictCase(this.text);
this.text = value;
if (caseText == caseValue) return value; // cancel redraw
if (caseValue.indexOf(caseText) == 0)
{
// new text is just old text with additions at the end, append the difference
appendTextCased(caseValue.substr(caseText.length));
return this.text;
}
value = caseValue;
group.kill();
if (value == '') return this.text;
appendTextCased(caseValue);
return this.text;
}
/**
* Adds new characters, without needing to redraw the previous characters
* @param str The text to add.
* @throws String if `text` is null.
*/
public function appendText(str:String):Void
{
if (str == null) throw 'cannot append null';
if (str == '') return;
this.text += str;
}
/**
* Converts all characters to fit the font's `allowedCase`.
* @param str
*/
function restrictCase(str:String):String
{
return switch (caseAllowed)
{
case Both:
str;
case Upper:
str.toUpperCase();
case Lower:
str.toLowerCase();
}
}
/**
* Adds new text on top of the existing text. Helper for other methods; DOESN'T CHANGE `this.text`.
* @param str The text to add, assumed to match the font's `caseAllowed`.
*/
function appendTextCased(str:String):Void
{
var charCount:Int = group.countLiving();
var xPos:Float = 0;
var yPos:Float = 0;
// `countLiving` returns -1 if group is empty
if (charCount == -1)
{
charCount = 0;
}
else if (charCount > 0)
{
var lastChar:AtlasChar = group.members[charCount - 1];
xPos = lastChar.x + lastChar.width - x;
yPos = lastChar.y + lastChar.height - maxHeight - y;
}
for (splitStr in str.split(''))
{
switch (splitStr)
{
case ' ':
xPos += 40;
case '\n':
xPos = 0;
yPos += maxHeight;
case char:
var charSprite:AtlasChar;
if (group.members.length <= charCount)
{
charSprite = new AtlasChar(atlas, char);
}
else
{
charSprite = group.members[charCount];
charSprite.revive();
charSprite.char = char;
charSprite.alpha = 1; // gets multiplied when added
}
charSprite.x = xPos;
charSprite.y = yPos + maxHeight - charSprite.height;
add(charSprite);
xPos += charSprite.width;
charCount++;
}
}
}
public function getWidth():Int
{
var width:Int = 0;
for (char in this.text.split(''))
{
switch (char)
{
case ' ':
width += 40;
case '\n':
// no width for newline character
case char:
var sprite = new AtlasChar(atlas, char);
sprite.revive();
sprite.char = char;
sprite.alpha = 1;
width += Std.int(sprite.width);
}
}
return width;
}
override function toString():String
{
return 'InputItem, ' + FlxStringUtil.getDebugString([
LabelValuePair.weak('x', x),
LabelValuePair.weak('y', y),
LabelValuePair.weak('text', text)
]);
}
}
class AtlasChar extends FlxSprite
{
/**
* Which character in the font we are using
*/
public var char(default, set):String;
public function new(x = 0.0, y = 0.0, atlas:FlxAtlasFrames, char:String)
{
super(x, y);
frames = atlas;
this.char = char;
}
function set_char(value:String):String
{
if (this.char == value) return this.char;
animation.addByPrefix('anim', getAnimPrefix(value), 24);
if (animation.exists('anim')) animation.play('anim');
updateHitbox();
this.char = value;
return this.char;
}
function getAnimPrefix(char:String):String
{
return switch (char)
{
case '&':
return '-andpersand-';
case '😠':
'-angry faic-'; // TODO: Do multi-flag characters work?
case "'":
'-apostraphie-';
case '\\':
'-back slash-';
case ',':
'-comma-';
case '-':
'-dash-';
case '↓':
'-down arrow-'; // U+2193
case '”':
'-end quote-'; // U+0022
case '!':
'-exclamation point-'; // U+0021
case '/':
'-forward slash-'; // U+002F
case '>':
'-greater than-'; // U+003E
case '♥':
'-heart-'; // U+2665
case '♡':
'-heart-';
case '←':
'-left arrow-'; // U+2190
case '<':
'-less than-'; // U+003C
case '*':
'-multiply x-';
case '.':
'-period-'; // U+002E
case '?':
'-question mark-';
case '→':
'-right arrow-'; // U+2192
case '“':
'-start quote-';
case '↑':
'-up arrow-'; // U+2191
// Default to getting the character itself.
default:
char;
}
}
}
@:nullSafety
private class AtlasFontData
{
public static var upperChar = ~/^[A-Z]\d+$/;
public static var lowerChar = ~/^[a-z]\d+$/;
public var atlas:FlxAtlasFrames;
public var maxHeight:Float = 0.0;
public var caseAllowed:Case = Both;
public function new(name:AtlasFont)
{
atlas = Paths.getSparrowAtlas('fonts/${name}');
if (atlas == null)
{
FlxG.log.warn('Could not find font atlas for font "${name}".');
return;
}
atlas.parent.destroyOnNoUse = false;
atlas.parent.persist = true;
var containsUpper:Bool = false;
var containsLower:Bool = false;
for (frame in atlas.frames)
{
maxHeight = Math.max(maxHeight, frame.frame.height);
if (!containsUpper) containsUpper = upperChar.match(frame.name);
if (!containsLower) containsLower = lowerChar.match(frame.name);
}
if (containsUpper != containsLower) caseAllowed = containsUpper ? Upper : Lower;
}
}
enum Case
{
Both;
Upper;
Lower;
}
enum abstract AtlasFont(String) from String to String
{
public var DEFAULT = 'default';
public var BOLD = 'bold';
public var FREEPLAY_CLEAR = 'freeplay-clear';
}