-
Notifications
You must be signed in to change notification settings - Fork 0
UI Text Template System
A powerful expression-based text templating system for dynamic UI text rendering. Supports variables, mathematical expressions, conditionals, string manipulation, arrays, and extensive formatting options.
- Basic Syntax
- Data Types
- Variables
- Operators
- Built-in Functions
- Property Access
- Arrays
- Formatting Options
- Escape Sequences
- Examples
Templates use curly braces {} to embed dynamic expressions within static text.
"Hello, {username}!"
"FPS: {fps:.1} | Frame Time: {(frame_time * 1000):.2}ms"
"Status: {health > 50 ? 'Healthy' : 'Critical'}"
{expression:precision|format_options}
| Component | Required | Description |
|---|---|---|
expression |
Yes | Any valid expression (variable, math, function call, etc.) |
:precision |
No | Decimal precision for numbers (e.g., :.2 for 2 decimal places) |
|format_options |
No | Pipe-separated formatting flags |
The template system supports five data types:
| Type | Description | Examples |
|---|---|---|
| Number | 64-bit floating point |
42, 3.14, -17, 1.5e-3
|
| String | UTF-8 text |
"hello", 'world', `backticks`
|
| Boolean | True or false |
true, false
|
| Array | Ordered collection |
[1, 2, 3], ["a", "b"]
|
| Null | Absence of value |
null, nil, none
|
42 // Integer
3.14 // Floating point
-17 // Negative
1.5e-3 // Scientific notation (0.0015)
1_000_000 // Underscores for readability (1000000)
0xFF // Hexadecimal (255)
0b1010 // Binary (10)
0o77 // Octal (63)
Three quote styles are supported:
"double quotes"
'single quotes'
`backtick quotes`
Variables are referenced by name and looked up from the UI variable registry.
{player_name}
{health}
{is_paused}
If a variable doesn't exist, it evaluates to null.
| Operator | Description | Example | Result |
|---|---|---|---|
+ |
Addition / Concatenation | {5 + 3} |
8 |
- |
Subtraction | {10 - 4} |
6 |
* |
Multiplication | {6 * 7} |
42 |
/ |
Division | {15 / 4} |
3.75 |
% |
Modulo (remainder) | {17 % 5} |
2 |
** |
Exponentiation | {2 ** 10} |
1024 |
- (unary) |
Negation | {-value} |
Negated value |
{"Hello" + " " + "World"} // "Hello World"
{"abc" * 3} // "abcabcabc"
{3 * "xy"} // "xyxyxy"
| Operator | Description | Example |
|---|---|---|
== |
Equal (loose) | {a == b} |
!= |
Not equal (loose) | {a != b} |
=== |
Strict equal | {a === b} |
!== |
Strict not equal | {a !== b} |
< |
Less than | {a < b} |
> |
Greater than | {a > b} |
<= |
Less than or equal | {a <= b} |
>= |
Greater than or equal | {a >= b} |
Loose vs Strict Equality:
- Loose (
==): Compares numeric strings to numbers ("42" == 42istrue) - Strict (
===): Requires same type ("42" === 42isfalse)
| Operator | Description | Example |
|---|---|---|
&& |
Logical AND | {a && b} |
|| |
Logical OR | {a || b} |
! |
Logical NOT | {!a} |
| Type | Truthy | Falsy |
|---|---|---|
| Boolean | true |
false |
| Number | Non-zero, non-NaN |
0, NaN
|
| String | Non-empty | Empty string ""
|
| Array | Non-empty | Empty array []
|
| Null | Never | Always |
| Operator | Description | Example | Result |
|---|---|---|---|
& |
Bitwise AND | {12 & 10} |
8 |
| |
Bitwise OR | {12 | 10} |
14 |
^ |
Bitwise XOR | {12 ^ 10} |
6 |
~ |
Bitwise NOT | {~5} |
-6 |
<< |
Left shift | {1 << 4} |
16 |
>> |
Right shift | {16 >> 2} |
4 |
{condition ? value_if_true : value_if_false}
Examples:
{health > 50 ? "Healthy" : "Injured"}
{fps >= 60 ? "Smooth" : fps >= 30 ? "Okay" : "Laggy"}
{is_admin ? "Admin" : is_mod ? "Moderator" : "User"}
Returns the right-hand value if the left-hand value is null.
{username ?? "Guest"}
{config_value ?? default_value ?? "fallback"}
Chains function calls in a readable left-to-right flow.
{value |> function_name}
{value |> function_name(additional_args)}
Examples:
{score |> abs |> round}
{name |> trim |> upper}
{price |> format(2) |> padleft(10)}
From highest to lowest:
-
()Parentheses -
.[]()Property access, indexing, function calls -
!-~Unary operators -
**Exponentiation (right-associative) -
*/%Multiplication, division, modulo -
+-Addition, subtraction -
<<>>Bit shifts -
<><=>=Comparisons -
==!====!==Equality -
&Bitwise AND -
^Bitwise XOR -
|Bitwise OR -
&&Logical AND -
||Logical OR -
??Null coalescing -
? :Ternary -
|>Pipeline
| Function | Description | Example | Result |
|---|---|---|---|
abs(n) |
Absolute value | abs(-5) |
5 |
floor(n) |
Round down | floor(3.7) |
3 |
ceil(n) |
Round up | ceil(3.2) |
4 |
round(n, decimals?) |
Round to nearest | round(3.456, 2) |
3.46 |
trunc(n) |
Truncate decimal | trunc(3.9) |
3 |
sign(n) |
Sign of number | sign(-5) |
-1 |
fract(n) |
Fractional part | fract(3.75) |
0.75 |
| Function | Description | Example | Result |
|---|---|---|---|
sqrt(n) |
Square root | sqrt(16) |
4 |
cbrt(n) |
Cube root | cbrt(27) |
3 |
pow(base, exp) |
Power | pow(2, 8) |
256 |
exp(n) |
e^n | exp(1) |
2.718... |
hypot(a, b) |
Hypotenuse | hypot(3, 4) |
5 |
| Function | Description | Example | Result |
|---|---|---|---|
ln(n) |
Natural log | ln(e()) |
1 |
log(n, base?) |
Logarithm (default base 10) | log(100) |
2 |
log2(n) |
Log base 2 | log2(8) |
3 |
log10(n) |
Log base 10 | log10(1000) |
3 |
| Function | Description | Example |
|---|---|---|
sin(n) |
Sine (radians) | sin(pi() / 2) |
cos(n) |
Cosine (radians) | cos(0) |
tan(n) |
Tangent (radians) | tan(pi() / 4) |
asin(n) |
Arcsine | asin(1) |
acos(n) |
Arccosine | acos(0) |
atan(n) |
Arctangent | atan(1) |
atan2(y, x) |
Two-argument arctangent | atan2(1, 1) |
sinh(n) |
Hyperbolic sine | sinh(1) |
cosh(n) |
Hyperbolic cosine | cosh(0) |
tanh(n) |
Hyperbolic tangent | tanh(1) |
degrees(rad) |
Radians to degrees |
degrees(pi()) → 180
|
radians(deg) |
Degrees to radians |
radians(180) → π
|
| Function | Description | Example | Result |
|---|---|---|---|
min(a, b, ...) |
Minimum value | min(5, 2, 8, 1) |
1 |
max(a, b, ...) |
Maximum value | max(5, 2, 8, 1) |
8 |
sum(a, b, ...) |
Sum of values | sum(1, 2, 3, 4) |
10 |
avg(a, b, ...) |
Average of values | avg(2, 4, 6) |
4 |
clamp(val, min, max) |
Clamp to range | clamp(15, 0, 10) |
10 |
| Function | Description | Example |
|---|---|---|
lerp(a, b, t) |
Linear interpolation |
lerp(0, 100, 0.5) → 50
|
map(val, inMin, inMax, outMin, outMax) |
Remap value range |
map(5, 0, 10, 0, 100) → 50
|
| Function | Description | Value |
|---|---|---|
pi() |
Pi | 3.14159... |
e() |
Euler's number | 2.71828... |
tau() |
Tau (2π) | 6.28318... |
inf() |
Infinity | ∞ |
nan() |
Not a Number | NaN |
| Function | Description | Example |
|---|---|---|
isnan(n) |
Is NaN? |
isnan(0/0) → true
|
isinf(n) |
Is infinite? |
isinf(1/0) → true
|
isfinite(n) |
Is finite? |
isfinite(42) → true
|
| Function | Description | Example | Result |
|---|---|---|---|
upper(s) |
Uppercase | upper("hello") |
"HELLO" |
lower(s) |
Lowercase | lower("HELLO") |
"hello" |
capitalize(s) |
Capitalize first letter | capitalize("hello") |
"Hello" |
title(s) |
Title case | title("hello world") |
"Hello World" |
| Function | Description | Example | Result |
|---|---|---|---|
trim(s) |
Trim both ends | trim(" hi ") |
"hi" |
ltrim(s) |
Trim left | ltrim(" hi ") |
"hi " |
rtrim(s) |
Trim right | rtrim(" hi ") |
" hi" |
| Function | Description | Example | Result |
|---|---|---|---|
reverse(s) |
Reverse string | reverse("hello") |
"olleh" |
repeat(s, n) |
Repeat n times | repeat("ab", 3) |
"ababab" |
replace(s, from, to) |
Replace substring | replace("hello", "l", "w") |
"hewwo" |
substr(s, start, len?) |
Substring | substr("hello", 1, 3) |
"ell" |
| Function | Description | Example | Result |
|---|---|---|---|
split(s, delim?) |
Split to array | split("a,b,c", ",") |
["a","b","c"] |
join(arr, delim?) |
Join array | join(["a","b"], "-") |
"a-b" |
| Function | Description | Example | Result |
|---|---|---|---|
len(s) |
Length | len("hello") |
5 |
contains(s, needle) |
Contains substring? | contains("hello", "ell") |
true |
startswith(s, prefix) |
Starts with? | startswith("hello", "he") |
true |
endswith(s, suffix) |
Ends with? | endswith("hello", "lo") |
true |
indexof(s, needle) |
Find index (-1 if not found) | indexof("hello", "l") |
2 |
| Function | Description | Example | Result |
|---|---|---|---|
padleft(s, width, char?) |
Pad left | padleft("42", 5, "0") |
"00042" |
padright(s, width, char?) |
Pad right | padright("hi", 5) |
"hi " |
center(s, width, char?) |
Center | center("hi", 6) |
" hi " |
| Function | Description | Example | Result |
|---|---|---|---|
char(code) |
Code point to character | char(65) |
"A" |
ord(s) |
Character to code point | ord("A") |
65 |
| Function | Description | Example | Result |
|---|---|---|---|
hex(n) |
To hexadecimal | hex(255) |
"ff" |
bin(n) |
To binary | bin(10) |
"1010" |
oct(n) |
To octal | oct(64) |
"100" |
| Function | Description | Example | Result |
|---|---|---|---|
len(arr) |
Array length | len([1,2,3]) |
3 |
first(arr) |
First element | first([1,2,3]) |
1 |
last(arr) |
Last element | last([1,2,3]) |
3 |
sum(arr) |
Sum of elements | sum([1,2,3]) |
6 |
avg(arr) |
Average | avg([2,4,6]) |
4 |
count(arr) |
Count elements | count([1,2,3]) |
3 |
reverse(arr) |
Reverse array | reverse([1,2,3]) |
[3,2,1] |
slice(arr, start, end?) |
Extract portion | slice([1,2,3,4], 1, 3) |
[2,3] |
range(start, end, step?) |
Generate range | range(0, 5) |
[0,1,2,3,4] |
contains(arr, val) |
Contains value? | contains([1,2,3], 2) |
true |
indexof(arr, val) |
Find index | indexof([1,2,3], 2) |
1 |
join(arr, delim?) |
Join to string | join([1,2,3], "-") |
"1-2-3" |
| Function | Description | Example | Result |
|---|---|---|---|
type(val) |
Get type name | type(42) |
"number" |
typeof(val) |
Get type name (alias) | typeof("hi") |
"string" |
isnull(val) |
Is null? | isnull(null) |
true |
isnum(val) |
Is number? | isnum(42) |
true |
isstr(val) |
Is string? | isstr("hi") |
true |
isbool(val) |
Is boolean? | isbool(true) |
true |
isarray(val) |
Is array? | isarray([1,2]) |
true |
defined(val) |
Is not null? | defined(x) |
true/false
|
empty(val) |
Is empty? | empty("") |
true |
| Function | Description | Example | Result |
|---|---|---|---|
num(val) |
Convert to number | num("42") |
42 |
str(val) |
Convert to string | str(42) |
"42" |
bool(val) |
Convert to boolean | bool(1) |
true |
int(val) |
Convert to integer | int(3.9) |
3 |
float(val) |
Convert to float | float("3.14") |
3.14 |
| Function | Description | Example | Result |
|---|---|---|---|
format(val, precision?) |
Format number | format(3.14159, 2) |
"3.14" |
comma(n, decimals?) |
Thousands separator | comma(1234567, 2) |
"1,234,567.00" |
percent(n, decimals?) |
Percentage | percent(0.75, 1) |
"75.0%" |
currency(n, symbol?, dec?) |
Currency format | currency(1234.5, "$", 2) |
"$1,234.50" |
ordinal(n) |
Ordinal suffix | ordinal(3) |
"3rd" |
bytes(n, decimals?) |
Human-readable bytes | bytes(1536, 1) |
"1.5 KB" |
duration(secs) |
Format as duration | duration(3661) |
"1:01:01" |
elapsed(secs) |
Human-readable time | elapsed(3600) |
"1.0 hours" |
| Function | Description | Example |
|---|---|---|
if(cond, yes, no?) |
Conditional | if(x > 0, "pos", "neg") |
ifnull(val, default) |
Default if null | ifnull(name, "Unknown") |
default(val, fallback) |
Default if null/empty | default(input, "N/A") |
coalesce(a, b, ...) |
First non-null | coalesce(x, y, z) |
choose(idx, a, b, ...) |
Select by index |
choose(1, "a", "b", "c") → "b"
|
switch(val, case1, res1, ...) |
Switch/case | switch(x, 1, "one", 2, "two", "other") |
| Function | Description | Example |
|---|---|---|
debug(val) |
Debug representation | debug([1,2]) |
Values support property access using dot notation.
| Property | Description | Example | Result |
|---|---|---|---|
.length / .len
|
String length | "hello".length |
5 |
.upper |
Uppercase | "hello".upper |
"HELLO" |
.lower |
Lowercase | "HELLO".lower |
"hello" |
.trim |
Trimmed | " hi ".trim |
"hi" |
.reverse |
Reversed | "hello".reverse |
"olleh" |
.first |
First character | "hello".first |
"h" |
.last |
Last character | "hello".last |
"o" |
.empty |
Is empty? | "".empty |
true |
.chars |
Array of characters | "hi".chars |
["h","i"] |
.words |
Array of words | "a b".words |
["a","b"] |
.lines |
Array of lines | "a\nb".lines |
["a","b"] |
| Property | Description | Example | Result |
|---|---|---|---|
.length / .len / .count
|
Array length | [1,2,3].length |
3 |
.first |
First element | [1,2,3].first |
1 |
.last |
Last element | [1,2,3].last |
3 |
.empty |
Is empty? | [].empty |
true |
.sum |
Sum of elements | [1,2,3].sum |
6 |
.avg |
Average | [2,4,6].avg |
4 |
.min |
Minimum | [3,1,2].min |
1 |
.max |
Maximum | [3,1,2].max |
3 |
.reverse |
Reversed | [1,2,3].reverse |
[3,2,1] |
| Property | Description | Example | Result |
|---|---|---|---|
.abs |
Absolute value | (-5).abs |
5 |
.floor |
Floor | (3.7).floor |
3 |
.ceil |
Ceiling | (3.2).ceil |
4 |
.round |
Rounded | (3.5).round |
4 |
.trunc |
Truncated | (3.9).trunc |
3 |
.sqrt |
Square root | (16).sqrt |
4 |
.sign |
Sign | (-5).sign |
-1 |
.fract |
Fractional part | (3.75).fract |
0.75 |
.int |
Integer part | (3.9).int |
3 |
.neg |
Negated | (5).neg |
-5 |
.isnan |
Is NaN? | (0/0).isnan |
true |
.isinf |
Is infinite? | (1/0).isinf |
true |
.isfinite |
Is finite? | (42).isfinite |
true |
.hex |
Hexadecimal | (255).hex |
"ff" |
.bin |
Binary | (10).bin |
"1010" |
.oct |
Octal | (64).oct |
"100" |
{[1, 2, 3, 4, 5]}
{["apple", "banana", "cherry"]}
{[true, false, null]}
{[1, "mixed", true]}
{[1..5]} // Exclusive: [1, 2, 3, 4]
{[1..=5]} // Inclusive: [1, 2, 3, 4, 5]
{[0..10]} // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
{arr[0]} // First element
{arr[1]} // Second element
{arr[-1]} // Last element
{arr[-2]} // Second to last
{"hello"[0]} // "h"
{"hello"[-1]} // "o"
{[1, 2] + [3, 4]} // [1, 2, 3, 4]
{[1, 2] + 3} // [1, 2, 3]
{0 + [1, 2]} // [0, 1, 2]
Format options are specified after a | character in the placeholder.
{expression|option1|option2|...}
{expression:.precision|option1|option2}
| Syntax | Description | Example | Result |
|---|---|---|---|
:.N |
N decimal places | {3.14159:.2} |
3.14 |
:N |
N decimal places | {3.14159:2} |
3.14 |
| Option | Description | Example | Result |
|---|---|---|---|
sign or +
|
Always show sign | {5|sign} |
+5 |
space |
Space for positive | {5|space} |
5 |
| Option | Description | Example | Result |
|---|---|---|---|
width=N or wN
|
Minimum width | {5|width=4} |
5 |
pad=X |
Padding character | {5|width=4|pad=0} |
0005 |
left or -
|
Left align | {5|width=4|left} |
5 |
| Option | Description | Example | Result |
|---|---|---|---|
upper |
Uppercase | {"hello"|upper} |
HELLO |
lower |
Lowercase | {"HELLO"|lower} |
hello |
| Option | Description | Example | Result |
|---|---|---|---|
hex or x
|
Hexadecimal | {255|hex} |
ff |
HEX or X
|
Uppercase hex | {255|HEX} |
FF |
bin or b
|
Binary | {10|bin} |
1010 |
oct or o
|
Octal | {64|oct} |
100 |
| Option | Description | Example | Result |
|---|---|---|---|
exp or e
|
Scientific notation | {1234|exp} |
1.234000e3 |
percent or %
|
Percentage | {0.75:.1|%} |
75.0% |
| Option | Description |
|---|---|
fix |
Enable fixed-width mode |
int=N |
Integer part width |
dec=N |
Decimal part width |
Example:
{value|fix|int=4|dec=2} // " 42.50"
{-value|fix|int=4|dec=2} // " -42.50"
{fps:.1} // "60.0"
{fps:.1|sign} // "+60.0"
{score|width=8|pad=0} // "00001234"
{percentage:.1|%} // "75.5%"
{hex_value|HEX|width=4|pad=0} // "00FF"
{name|upper|width=20} // "JOHN "
{value|fix|int=6|dec=3|sign} // " +123.456"
Use double braces to include literal braces in output:
"{{value}}" → "{value}"
"}}" → "}"
Within string literals:
| Sequence | Character |
|---|---|
\\ |
Backslash |
\" |
Double quote |
\' |
Single quote |
\n |
Newline |
\t |
Tab |
\r |
Carriage return |
\0 |
Null character |
\xNN |
Hex character code |
Examples:
"Line 1\nLine 2"
"Tab\there"
"Quote: \"hello\""
"\x41\x42\x43" → "ABC"
"Player: {player_name}"
"Health: {health}/{max_health}"
"Score: {score}"
"Total: {price * quantity}"
"Average: {(a + b + c) / 3:.2}"
"Distance: {sqrt(x*x + y*y):.1}m"
"Percentage: {(current / total * 100):.1}%"
"Status: {health > 75 ? 'Healthy' : health > 25 ? 'Injured' : 'Critical'}"
"Connection: {is_online ? 'Online' : 'Offline'}"
"{is_debug && show_fps ? 'FPS: ' + fps : ''}"
"FPS: {fps:.1}"
"Frame Time: {(frame_time * 1000):.2}ms"
"Memory: {bytes(memory_used, 1)}"
"Price: {currency(price, '$', 2)}"
"Progress: {percent(progress, 1)}"
"Score: {comma(score)}"
"Hello, {upper(name)}!"
"Username: {trim(input)}"
"{padleft(level, 3, '0')}"
"Initials: {first(first_name) + first(last_name)}"
"FPS: {fps:.1} | Frame: {(render_dt * 1000):.2}ms | {fps >= 60 ? 'Smooth' : 'Laggy'}"
"Position: ({x:.1}, {y:.1}, {z:.1})"
"Player {player_name ?? 'Unknown'} - Level {level} {title(class)}"
"{is_debug ? '[DEBUG] ' : ''}{message}"
"HP: {padleft(str(health), 3)} / {max_health} [{repeat('█', round(health/max_health*10))}{repeat('░', 10 - round(health/max_health*10))}]"
{name |> trim |> upper}
{score |> abs |> comma}
{data |> first |> format(2)}
{input |> trim |> lower |> capitalize}
"Items: {join(items, ', ')}"
"First: {first(scores)}, Last: {last(scores)}"
"Total: {sum(values)}, Average: {avg(values):.2}"
"Range: {join([1..=5], '-')}" // "1-2-3-4-5"
"X: {x|fix|int=6|dec=2|sign}" // "X: +123.45"
"Y: {y|fix|int=6|dec=2|sign}" // "Y: -67.89"
"Z: {z|fix|int=6|dec=2|sign}" // "Z: +0.00"
+ - * / % ** Arithmetic
== != === !== < > <= >= Comparison
&& || ! Logical
& | ^ ~ << >> Bitwise
? : Ternary
?? Null coalescing
|> Pipeline
abs ceil floor round trunc sqrt pow min max clamp lerp
len upper lower trim replace split join substr contains
first last sum avg count range slice reverse
format comma percent currency bytes duration
if ifnull default coalesce switch
type isnum isstr isbool isnull isarray
:.N Precision (N decimals)
|sign Show + for positive
|hex Hexadecimal output
|bin Binary output
|exp Scientific notation
|% Percentage
|upper Uppercase
|lower Lowercase
|width=N Minimum width
|pad=X Padding character
|left Left align
|fix Fixed-width mode
This page was written with claude-opus-4-5-20251101 btw, pretty good! So that I don't have to waste my time on wiki and instead focus on game development!