Created
March 15, 2021 20:17
-
-
Save MattRix/9bb2f47687b9e64adc8e8ed490149532 to your computer and use it in GitHub Desktop.
FLabel with pixel snapping and wrapping
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
using System.Collections; | |
using System.Collections.Generic; | |
//parts of this were inspired by https://github.com/prime31/UIToolkit/blob/master/Assets/Plugins/UIToolkit/UIElements/UIText.cs | |
public class FLabel : FFacetElementNode | |
{ | |
public Action SignalTextChange; | |
public static float defaultAnchorX = 0.5f; | |
public static float defaultAnchorY = 0.5f; | |
protected FFont _font; | |
protected string _fontName; | |
protected string _text; | |
protected Color _color = Futile.white; | |
protected Color _alphaColor = Futile.white; | |
protected FLetterQuadLine[] _letterQuadLines; | |
protected bool _isMeshDirty = false; | |
protected float _anchorX = defaultAnchorX; | |
protected float _anchorY = defaultAnchorY; | |
protected bool _doesTextNeedUpdate = false; | |
protected bool _doesLocalPositionNeedUpdate = false; | |
protected bool _doQuadsNeedUpdate = false; | |
protected Rect _textRect; | |
protected FTextParams _textParams; | |
protected bool _shouldSnapToPixels = false; | |
public FLabel (string fontName, string text) : this(fontName, text, new FTextParams()) | |
{ | |
} | |
public FLabel (string fontName, string text, FTextParams textParams) : base() | |
{ | |
_fontName = fontName; | |
_text = text; | |
_font = Futile.atlasManager.GetFontWithName(_fontName); | |
_textParams = textParams; | |
Init(FFacetType.Quad, _font.element, 0); | |
CreateTextQuads(); | |
} | |
public string WrapTextToWidth(string newText, float maxWidth) | |
{ | |
string newLabelText = newText; | |
string[] words = newText.Split(' '); | |
if (words.Length <= 1) | |
{ | |
// Only one word (or zero?), so just set the label | |
text = newText; | |
} | |
else | |
{ | |
newLabelText = words[0]; | |
text = newLabelText; | |
// Add words, one at a time, to the label until it's too wide, then word wrap. | |
for (int i = 1; i < words.Length; i++) | |
{ | |
string prevText = newLabelText; | |
newLabelText = prevText + " " + words[i]; | |
text = newLabelText; | |
if (textRect.width > maxWidth) | |
{ | |
newLabelText = prevText + "\n" + words[i]; | |
} | |
} | |
text = newLabelText; | |
} | |
return newLabelText; | |
} | |
public void CreateTextQuads() | |
{ | |
_doesTextNeedUpdate = false; | |
int oldFacetsNeeded = _numberOfFacetsNeeded; | |
_letterQuadLines = _font.GetQuadInfoForText(_text,_textParams); | |
_numberOfFacetsNeeded = 0; | |
int lineCount = _letterQuadLines.Length; | |
for(int i = 0; i< lineCount; i++) | |
{ | |
_numberOfFacetsNeeded += _letterQuadLines[i].quads.Length; | |
} | |
if(_isOnStage) | |
{ | |
int delta = _numberOfFacetsNeeded - oldFacetsNeeded; | |
if(delta != 0) //if the number of letter quads has changed, tell the stage | |
{ | |
_stage.HandleFacetsChanged(); | |
} | |
} | |
UpdateLocalPosition(); //figures out the bounds and alignment, and sets the mesh dirty | |
} | |
public void UpdateLocalPosition() | |
{ | |
_doesLocalPositionNeedUpdate = false; | |
float minY = float.MaxValue; | |
float maxY = float.MinValue; | |
float minX = float.MaxValue; | |
float maxX = float.MinValue; | |
int lineCount = _letterQuadLines.Length; | |
for(int i = 0; i<lineCount; i++) | |
{ | |
FLetterQuadLine line = _letterQuadLines[i]; | |
minY = Math.Min (line.bounds.yMin,minY); | |
maxY = Math.Max (line.bounds.yMax,maxY); | |
} | |
float offsetY = -(minY + ((maxY-minY)*_anchorY)); | |
for(int i = 0; i<lineCount; i++) | |
{ | |
FLetterQuadLine line = _letterQuadLines[i]; | |
float offsetX = -line.bounds.width*_anchorX; | |
minX = Math.Min (offsetX,minX); | |
maxX = Math.Max (offsetX+line.bounds.width,maxX); | |
int quadCount = line.quads.Length; | |
for(int q = 0; q< quadCount; q++) | |
{ | |
line.quads[q].CalculateVectors(offsetX+_font.offsetX, offsetY+_font.offsetY); | |
} | |
} | |
_textRect.x = minX; | |
_textRect.y = minY+offsetY; | |
_textRect.width = maxX-minX; | |
_textRect.height = maxY-minY; | |
_isMeshDirty = true; | |
} | |
override public void Redraw(bool shouldForceDirty, bool shouldUpdateDepth) | |
{ | |
bool wasMatrixDirty = _isMatrixDirty; | |
bool wasAlphaDirty = _isAlphaDirty; | |
UpdateDepthMatrixAlpha(shouldForceDirty, shouldUpdateDepth); | |
if(shouldUpdateDepth) | |
{ | |
UpdateFacets(); | |
} | |
if(wasMatrixDirty || shouldForceDirty || shouldUpdateDepth) | |
{ | |
_isMeshDirty = true; | |
} | |
if(wasAlphaDirty || shouldForceDirty) | |
{ | |
_isMeshDirty = true; | |
_alphaColor = _color.CloneWithMultipliedAlpha(_concatenatedAlpha); | |
} | |
if(_doesLocalPositionNeedUpdate) | |
{ | |
UpdateLocalPosition(); | |
} | |
if(_isMeshDirty) | |
{ | |
PopulateRenderLayer(); | |
} | |
} | |
override public void PopulateRenderLayer() | |
{ | |
if(_isOnStage && _firstFacetIndex != -1) | |
{ | |
_isMeshDirty = false; | |
//check if the label is empty so we don't have to bother trying to draw anything | |
if(_letterQuadLines.Length == 0 || _letterQuadLines[0].quads.Length == 0) | |
{ | |
_renderLayer.HandleVertsChange(); | |
return; | |
} | |
Vector3[] vertices = _renderLayer.vertices; | |
Vector2[] uvs = _renderLayer.uvs; | |
Color[] colors = _renderLayer.colors; | |
int vertexIndex0 = _firstFacetIndex*4; | |
int vertexIndex1 = vertexIndex0 + 1; | |
int vertexIndex2 = vertexIndex0 + 2; | |
int vertexIndex3 = vertexIndex0 + 3; | |
Vector2 topLeft = _letterQuadLines[0].quads[0].topLeft; | |
FMatrix matrixToUse = _concatenatedMatrix; | |
if(_shouldSnapToPixels) | |
{ | |
matrixToUse = matrixToUse.Clone(); | |
matrixToUse.tx += (Mathf.Round(topLeft.x * Futile.displayScale) * Futile.displayScaleInverse) - topLeft.x; | |
matrixToUse.ty += (Mathf.Round(topLeft.y * Futile.displayScale) * Futile.displayScaleInverse) - topLeft.y; | |
} | |
int lineCount = _letterQuadLines.Length; | |
for(int i = 0; i<lineCount; i++) | |
{ | |
FLetterQuad[] quads = _letterQuadLines[i].quads; | |
int quadCount = quads.Length; | |
for(int q = 0; q<quadCount; q++) | |
{ | |
FLetterQuad quad = quads[q]; | |
FCharInfo charInfo = quad.charInfo; | |
matrixToUse.ApplyVector3FromLocalVector2(ref vertices[vertexIndex0], quad.topLeft,0); | |
matrixToUse.ApplyVector3FromLocalVector2(ref vertices[vertexIndex1], quad.topRight,0); | |
matrixToUse.ApplyVector3FromLocalVector2(ref vertices[vertexIndex2], quad.bottomRight,0); | |
matrixToUse.ApplyVector3FromLocalVector2(ref vertices[vertexIndex3], quad.bottomLeft,0); | |
uvs[vertexIndex0] = charInfo.uvTopLeft; | |
uvs[vertexIndex1] = charInfo.uvTopRight; | |
uvs[vertexIndex2] = charInfo.uvBottomRight; | |
uvs[vertexIndex3] = charInfo.uvBottomLeft; | |
colors[vertexIndex0] = _alphaColor; | |
colors[vertexIndex1] = _alphaColor; | |
colors[vertexIndex2] = _alphaColor; | |
colors[vertexIndex3] = _alphaColor; | |
vertexIndex0 += 4; | |
vertexIndex1 += 4; | |
vertexIndex2 += 4; | |
vertexIndex3 += 4; | |
} | |
} | |
_renderLayer.HandleVertsChange(); | |
} | |
} | |
public FLabelAlignment alignment | |
{ | |
get | |
{ | |
if (_anchorX == 0.5f) return FLabelAlignment.Center; | |
if (_anchorX == 0.0f) return FLabelAlignment.Left; | |
if (_anchorX == 1.0f) return FLabelAlignment.Right; | |
return FLabelAlignment.Custom; | |
} | |
set | |
{ | |
if (value == FLabelAlignment.Center) this.anchorX = 0.5f; | |
else if (value == FLabelAlignment.Left) this.anchorX = 0.0f; | |
else if (value == FLabelAlignment.Right) this.anchorX = 1.0f; | |
} | |
} | |
virtual public string text | |
{ | |
get {return _text;} | |
set | |
{ | |
if(_text != value) | |
{ | |
_text = value; | |
_doesTextNeedUpdate = true; | |
CreateTextQuads(); //lazily creating the quads was causing too many issues, so just create them when .text is set | |
if(SignalTextChange != null) SignalTextChange(); | |
} | |
} | |
} | |
public float anchorX | |
{ | |
get {return _anchorX;} | |
set | |
{ | |
if(_anchorX != value) | |
{ | |
_anchorX = value; | |
_doesLocalPositionNeedUpdate = true; | |
} | |
} | |
} | |
public float anchorY | |
{ | |
get {return _anchorY;} | |
set | |
{ | |
if(_anchorY != value) | |
{ | |
_anchorY = value; | |
_doesLocalPositionNeedUpdate = true; | |
} | |
} | |
} | |
virtual public Color color | |
{ | |
get { return _color; } | |
set | |
{ | |
if(_color != value) | |
{ | |
_color = value; | |
_isAlphaDirty = true; | |
} | |
} | |
} | |
virtual public Rect textRect | |
{ | |
get | |
{ | |
if(_doesTextNeedUpdate) CreateTextQuads(); | |
if(_doesLocalPositionNeedUpdate) UpdateLocalPosition(); | |
return _textRect; | |
} | |
} | |
public bool shouldSnapToPixels | |
{ | |
get { return _shouldSnapToPixels; } | |
set { _shouldSnapToPixels = value; } | |
} | |
[Obsolete("FLabel's boundsRect is obsolete, use textRect instead")] | |
public Rect boundsRect | |
{ | |
get {throw new NotSupportedException("boundsRect is obsolete! Use textRect instead");} | |
} | |
//for convenience | |
public void SetAnchor(float newX, float newY) | |
{ | |
this.anchorX = newX; | |
this.anchorY = newY; | |
} | |
public void SetAnchor(Vector2 newAnchor) | |
{ | |
this.anchorX = newAnchor.x; | |
this.anchorY = newAnchor.y; | |
} | |
public Vector2 GetAnchor() | |
{ | |
return new Vector2(_anchorX,_anchorY); | |
} | |
} | |
public enum FLabelAlignment | |
{ | |
Center, | |
Left, | |
Right, | |
Custom | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment