Last active
May 27, 2025 19:42
-
-
Save neoRiley/44c8836942272c8456d647449130e562 to your computer and use it in GitHub Desktop.
`ScalableFontLabel.cs` is a subclass of TextElement (Label etc) and gives you extra properties to dial in your font scaling. If you just want to create it with code, I've added an extension method. Be sure to grab all 3 files regardless. Add the 3 files to your project, and you should be able to add via the UI Builder IDE
This file contains hidden or 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.UIElements; | |
using Utils.Extensions; | |
namespace N30R1L37.UI | |
{ | |
[UxmlElement] | |
public partial class ScalableFontLabel : TextElement, IDisposable | |
{ | |
private TextElementScaler _scaler; | |
private TextScaleMode _scaleMode = TextScaleMode.Both; | |
[UxmlAttribute("scale-direction")] | |
public TextScaleMode ScaleMode | |
{ | |
get => _scaleMode; | |
set | |
{ | |
_scaleMode = value; | |
} | |
} | |
private float _scale = 1.0f; | |
[UxmlAttribute("scale")] | |
public float Scale | |
{ | |
get => _scale; | |
set | |
{ | |
_scale = Math.Max(0.01f, value); | |
if (_scaler == null) return; | |
_scaler.SetScale(value); | |
} | |
} | |
private float _charWidth = 0.695f; | |
[UxmlAttribute("char-width")] | |
public float CharWidth | |
{ | |
get => _charWidth; | |
set | |
{ | |
_charWidth = Math.Clamp(value, 0.5f, 0.695f); | |
if (_scaler == null) return; | |
_scaler.SetCharWidth(value); | |
} | |
} | |
private float _charHeight = 1; | |
[UxmlAttribute("char-height")] | |
public float CharHeight | |
{ | |
get => _charHeight; | |
set | |
{ | |
_charHeight = Math.Clamp(value, 0.5f, 1f); | |
if (_scaler == null) return; | |
_scaler.SetCharHeight(value); | |
} | |
} | |
public ScalableFontLabel() | |
{ | |
RegisterCallback<AttachToPanelEvent>(OnAttachToPanel); | |
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanel); | |
} | |
private void OnAttachToPanel(AttachToPanelEvent evt) | |
{ | |
if (evt == null) return; | |
_scaler = this.EnableScaling(this, Scale, ScaleMode); | |
} | |
private void OnDetachFromPanel(DetachFromPanelEvent evt) | |
{ | |
Dispose(); | |
} | |
public void Dispose() | |
{ | |
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanel); | |
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanel); | |
} | |
} | |
} |
This file contains hidden or 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
namespace N30R1L37.UI | |
{ | |
using UnityEngine; | |
using UnityEngine.UIElements; | |
public enum TextScaleMode | |
{ | |
Both, | |
WidthOnly, | |
HeightOnly | |
} | |
public class TextElementScaler | |
{ | |
private VisualElement _parent; | |
private TextElement _textElement; | |
private float _scale; | |
private TextScaleMode _scaleMode; | |
private float _baseFontSize; | |
// Heuristic estimate for character dimensions at fontSize = 1 | |
private float _charWidth = 0.695f; // adjust if needed per font: 0.5f to 0.695f is acceptable | |
private float _charHeight = 1f; // adjust if needed per font | |
public TextElementScaler(VisualElement parent, TextElement textElement, float scale, TextScaleMode scaleMode) | |
{ | |
_parent = parent; | |
_textElement = textElement; | |
_scale = scale; | |
_scaleMode = scaleMode; | |
if (_textElement.style.fontSize.value.value > 0) | |
{ | |
_baseFontSize = _textElement.style.fontSize.value.value; | |
} | |
else | |
{ | |
_textElement.style.fontSize = 14f; | |
_baseFontSize = 14f; | |
} | |
_textElement.style.flexGrow = 1f; | |
_textElement.style.flexShrink = 0f; | |
_textElement.style.alignSelf = Align.Stretch; | |
_parent.RegisterCallback<GeometryChangedEvent>(OnParentResized); | |
UpdateFontSize(); | |
// 🔐 Store this scaler in userData to prevent GC | |
_textElement.userData = this; | |
} | |
public void SetScale(float value) | |
{ | |
_scale = value; | |
_parent.MarkDirtyRepaint(); | |
UpdateFontSize(); | |
} | |
public void SetCharWidth(float charWidth) | |
{ | |
_charWidth = charWidth; | |
_parent.MarkDirtyRepaint(); | |
UpdateFontSize(); | |
} | |
public void SetCharHeight(float charHeight) | |
{ | |
_charHeight = charHeight; | |
_parent.MarkDirtyRepaint(); | |
UpdateFontSize(); | |
} | |
private void OnParentResized(GeometryChangedEvent evt) | |
{ | |
UpdateFontSize(); | |
} | |
private void UpdateFontSize() | |
{ | |
if (string.IsNullOrEmpty(_textElement.text)) | |
return; | |
// Drawable area of the element itself | |
float elementWidth = _textElement.resolvedStyle.width | |
- _textElement.resolvedStyle.paddingLeft | |
- _textElement.resolvedStyle.paddingRight | |
- _textElement.resolvedStyle.borderLeftWidth | |
- _textElement.resolvedStyle.borderRightWidth; | |
float elementHeight = _textElement.resolvedStyle.height | |
- _textElement.resolvedStyle.paddingTop | |
- _textElement.resolvedStyle.paddingBottom | |
- _textElement.resolvedStyle.borderTopWidth | |
- _textElement.resolvedStyle.borderBottomWidth; | |
// Count real characters (no line breaks) | |
string text = _textElement.text.Replace("\n", ""); | |
int totalCharCount = text.Length; | |
// Estimate chars that can fit per line | |
int maxCharsPerLine = Mathf.Max(1, Mathf.FloorToInt(elementWidth / (_charWidth * _baseFontSize))); | |
// Estimate number of lines based on wrap + \n | |
int manualLineBreaks = _textElement.text.Split('\n').Length - 1; | |
int estimatedWrappedLines = Mathf.CeilToInt(totalCharCount / (float)maxCharsPerLine); | |
int totalLines = Mathf.Max(1, estimatedWrappedLines + manualLineBreaks); | |
// Height must now be divided by line count | |
float maxFontSizeByHeight = elementHeight / (totalLines * _charHeight); | |
// Width constraint remains the same | |
float maxFontSizeByWidth = elementWidth / (totalCharCount * _charWidth); | |
float maxFontSize = 0f; | |
switch (_scaleMode) | |
{ | |
case TextScaleMode.WidthOnly: | |
maxFontSize = Mathf.Min(maxFontSizeByWidth, maxFontSizeByHeight); | |
break; | |
case TextScaleMode.HeightOnly: | |
maxFontSize = maxFontSizeByHeight; | |
break; | |
case TextScaleMode.Both: | |
maxFontSize = Mathf.Min(maxFontSizeByWidth, maxFontSizeByHeight); | |
break; | |
} | |
float scaledFontSize = maxFontSize * _scale; | |
scaledFontSize = Mathf.Clamp(scaledFontSize, 1f, 200f); | |
_textElement.style.fontSize = scaledFontSize; | |
} | |
} | |
} |
This file contains hidden or 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 N30R1L37.UI; | |
using UnityEngine.UIElements; | |
namespace Utils.Extensions | |
{ | |
public static class TextScalerExtensions | |
{ | |
public static TextElementScaler EnableScaling(this TextElement element, VisualElement parent, float scale, TextScaleMode mode) | |
{ | |
return new TextElementScaler(parent, element, scale, mode); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment