AutoText
https://answers.unity.com/questions/1865976/ui-toolkit-text-best-fit.html
TS
import { Dom } from "OneJS/Dom"
import { h, render } from "preact"
import { useRef, useEffect } from "preact/hooks"
import { Mathf } from "UnityEngine"
import { Length, LengthUnit, MeshGenerationContext, StyleFloat, StyleLength, TextElement } from "UnityEngine/UIElements"
import { MeasureMode } from "UnityEngine/UIElements/VisualElement"
const AutoText = (props: { text?: string }) => {
const ref = useRef<Dom>();
let dirtyCount = 0;
useEffect(() => {
ref.current.ve.style.opacity = new StyleFloat(0);
ref.current.ve.generateVisualContent = onGenerateVisualContent
ref.current.ve.MarkDirtyRepaint()
}, [])
function onGenerateVisualContent(mgc: MeshGenerationContext) {
dirtyCount++
setTimeout(resize)
}
function resize() {
let contentRect = ref.current.ve.contentRect
let textElement = (ref.current.ve.Children() as any)[0] as TextElement
let textSize = textElement.MeasureTextSize(props.text, 99999, MeasureMode.AtMost, 99999, MeasureMode.AtMost);
let fontSize = Mathf.Max(ref.current.ve.style.fontSize.value.value, 1);
let heightDictatedFontSize = Mathf.Abs(contentRect.height);
let widthDictatedFontSize = Mathf.Abs(contentRect.width / textSize.x) * fontSize;
let newFontSize = Mathf.FloorToInt(Mathf.Min(heightDictatedFontSize, widthDictatedFontSize));
newFontSize = Mathf.Clamp(newFontSize, 1, 9999);
ref.current.ve.style.fontSize = new StyleLength(new Length(newFontSize, LengthUnit.Pixel))
if (dirtyCount > 1) {
ref.current.ve.style.opacity = new StyleFloat(1);
} else {
ref.current.ve.MarkDirtyRepaint()
}
}
return <div ref={ref} class="w-full h-full">{props.text}</div>
}
const App = () => {
return <div name="app" class="w-full h-full">
<AutoText text="Hey!!" />
</div>
}
render(<App />, document.body)
The dirtyCount
and opacity
usage is to get rid of the 1-frame flicker that may happen during resizing. This is because both Preact and UI Toolkit re-rendering has a 1 frame delay.
Here's another example based on a textfield
:
TYPESCRIPT
import { Dom } from "OneJS/Dom"
import { h, render } from "preact"
import { useEffect, useRef, useState } from "preact/hooks"
import { Mathf } from "UnityEngine";
import { StyleFloat, MeshGenerationContext, TextElement, StyleLength, Length, LengthUnit } from "UnityEngine/UIElements"
import { MeasureMode } from "UnityEngine/UIElements/VisualElement"
document.clearRuntimeStyleSheets() // For reloading
document.addRuntimeUSS(`
#unity-text-input {
padding: 0;
margin: 0;
}
#foo TextElement {
-unity-text-align: middle-center;
width: 100%;
}
`)
const App = () => {
const ref = useRef<Dom>()
const [text, setText] = useState("Hello World")
let dirtyCount = 0
useEffect(() => {
ref.current.ve.style.opacity = new StyleFloat(0)
ref.current.ve.generateVisualContent = onGenerateVisualContent
ref.current.ve.MarkDirtyRepaint()
}, [])
function onGenerateVisualContent(mgc: MeshGenerationContext) {
dirtyCount++
setTimeout(resize)
}
function resize() {
let textElement = (ref.current.ve.Children() as any)[0][0] as TextElement
let contentRect = textElement.contentRect
let textSize = textElement.MeasureTextSize(text, 99999, MeasureMode.AtMost, 99999, MeasureMode.AtMost);
let fontSize = Mathf.Max(ref.current.ve.style.fontSize.value.value, 1);
let heightDictatedFontSize = Mathf.Abs(contentRect.height);
let widthDictatedFontSize = Mathf.Abs(contentRect.width / textSize.x) * fontSize;
let newFontSize = Mathf.FloorToInt(Mathf.Min(heightDictatedFontSize, widthDictatedFontSize));
newFontSize = Mathf.Clamp(newFontSize, 1, 9999);
ref.current.ve.style.fontSize = new StyleLength(new Length(newFontSize, LengthUnit.Pixel))
if (dirtyCount > 1) {
ref.current.ve.style.opacity = new StyleFloat(1);
} else {
ref.current.ve.MarkDirtyRepaint()
}
}
return <div class="w-full h-full flex justify-center p-3">
<div class="mx-auto bg-white overflow-hidden" style={{ width: "80%", height: "80%" }}>
<div class="flex flex-col h-full">
<div class="shrink-0 h-full" style={{ height: "20%" }}>
<textfield ref={ref} id="foo" class="w-full h-full" text={text} style={{ fontSize: "80%" }} />
</div>
<div class="p-8 bg-red-200" style={{ height: "80%" }}></div>
</div>
</div>
</div>
}
render(<App />, document.body)