Skip to content

Instantly share code, notes, and snippets.

@MattRix
Created March 15, 2021 20:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MattRix/9bb2f47687b9e64adc8e8ed490149532 to your computer and use it in GitHub Desktop.
Save MattRix/9bb2f47687b9e64adc8e8ed490149532 to your computer and use it in GitHub Desktop.
FLabel with pixel snapping and wrapping
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