Ply has a built-in rich text system that lets you color, animate, and transform individual characters.
Requires the text-styling feature flag:
[dependencies]
ply-engine = { version = "1.0", features = ["text-styling"] }🔗Syntax
Wrap styled text in {tag|content}. The tag goes before the pipe, the
content after:
Use underscores to chain parameters:
Tags can nest. Inner tags override outer ones if they conflict:
Escaping: Use \ to insert literal {, }, |, or \:
🔗Properties
Static attributes on the wrapped text. These are optimized in rendering.
🔗color
Sets the text fill color. Accepts hex (#RRGGBB), RGB tuples
((r,g,b)), or named colors.
Named colors: white, black, lightgray, darkgray, red, orange, yellow,
lime, green, cyan, lightblue, blue, purple, magenta, brown, pink (case
insensitive).
🔗opacity
Makes text semi-transparent:
Properties combine, nest opacity inside a color tag:
🔗Effects
Per-character visual effects that create movement, shadows, or gradients.
🔗wave
Vertical sine wave:
| Parameter | What it does | Default |
|---|---|---|
w | Wavelength in characters | 3 |
f | Frequency (cycles/sec) | 0.5 |
s | Speed (chars/sec) — overrides f | — |
a | Amplitude (ratio of font size) | 0.3 |
p | Phase offset (0–1) | 0 |
r | Direction rotation in degrees | 0 |
🔗pulse
Characters grow and shrink in a wave:
| Parameter | What it does | Default |
|---|---|---|
w | Wavelength in characters | 2 |
f | Frequency (cycles/sec) | 0.6 |
s | Speed (chars/sec) — overrides f | — |
a | Scale amplitude | 0.15 |
p | Phase offset (0–1) | 0 |
🔗swing
Pendulum rotation per character:
| Parameter | What it does | Default |
|---|---|---|
w | Wavelength in characters | 3 |
f | Frequency (swings/sec) | 0.5 |
s | Speed (chars/sec) — overrides f | — |
a | Amplitude in degrees | 8 |
p | Phase offset (0–1) | 0 |
🔗jitter
Random character displacement:
| Parameter | What it does | Default |
|---|---|---|
radii | Horizontal,vertical offset (font ratio) | 0.5,0.5 |
rotation | Rotation of the jitter ellipse (degrees) | 0 |
🔗gradient
Cycling color gradient across characters:
The default is a full rainbow. Custom stops use position:color pairs:
ui.text("{gradient_stops=0:#FF0000,5:#FFC32C_speed=2|Fire text}", |t| t.font_size(24));
| Parameter | What it does | Default |
|---|---|---|
stops | Comma-separated pos:color | rainbow |
speed | Scroll speed (chars/sec) | 1 |
🔗shadow
Draws a duplicate behind each character:
ui.text("{shadow_color=#000000_offset=-0.1,0.1|Shadowed}", |t| t.font_size(24));| Parameter | What it does | Default |
|---|---|---|
color | Shadow color | black |
offset | X,Y offset (font ratio) | -0.3,0.3 |
scale | Shadow size multiplier | 1.0 |
🔗transform
Static per-character transform:
| Parameter | What it does | Default |
|---|---|---|
translate | X,Y offset (font size ratio) | 0,0 |
scale | X,Y size multiplier | 1.0 |
rotate | Rotation in degrees | 0 |
🔗hide
Prevents rendering entirely. Useful for reserving space or with animations:
🔗Animations
Time-based transitions tracked by a unique id. Every animation needs
either in (appear) or out (disappear).
🔗type
Typewriter reveal:
| Parameter | What it does | Default |
|---|---|---|
in/out | Direction (required) | — |
id | Unique identifier (required) | — |
speed | Characters per second | 8 |
delay | Delay before starting (seconds) | 0 |
cursor | Character to show as cursor | none |
Show a blinking cursor while typing:
🔗fade
Opacity transition, character by character:
| Parameter | What it does | Default |
|---|---|---|
in/out | Direction (required) | — |
id | Unique identifier (required) | — |
speed | Characters per second | 3 |
trail | Gradient length in characters | 3 |
delay | Delay before starting (seconds) | 0 |
🔗scale
Pop-in or pop-out by scaling each character:
| Parameter | What it does | Default |
|---|---|---|
in/out | Direction (required) | — |
id | Unique identifier (required) | — |
speed | Characters per second | 3 |
trail | Gradient length in characters | 3 |
delay | Delay before starting (seconds) | 0 |
🔗Combining styles
Stack tags to combine effects. Effects compose: transforms accumulate, colors override:
🔗Styled text input
The text styling syntax works inside text inputs too. When text-styling
is enabled, you can add styles and have the user interact with and see it rendered live.
Use .no_styles_movement() so the cursor skips over style tag boundaries, this is useful when you are highlighting code:
ui.element()
.id("styled_editor")
.width(grow!())
.height(fixed!(200.0))
.background_color(0x1A1A28)
.corner_radius(6.0)
.text_input(|t| t
.multiline(true)
.font_size(14)
.text_color(0xDDDDDD)
.no_styles_movement()
)
.empty();🔗Live highlighting
Build a highlighter that converts plain text to styled text, then apply
it on every frame. Use the styling module:
use ply_engine::text_input::styling;
fn highlight(plain: &str) -> String {
plain.split(' ').map(|word| {
if word.starts_with('#') {
format!("{{color=#FFC32C|{}}}", styling::escape_str(word))
} else {
styling::escape_str(word)
}
}).collect::<Vec<_>>().join(" ")
}
Apply each frame:
let raw = ply.get_text_value("styled_editor").to_string();
if !raw.is_empty() {
let plain = styling::strip_styling(&raw);
let highlighted = highlight(&plain);
if raw != highlighted {
let cursor = ply.get_cursor_pos("styled_editor");
let content_pos = styling::cursor_to_content(&raw, cursor);
ply.set_text_value("styled_editor", &highlighted);
let new_cursor = styling::content_to_cursor(&highlighted, content_pos, false);
ply.set_cursor_pos("styled_editor", new_cursor);
}
}🔗styling functions
| Function | What it does |
|---|---|
escape_str(s) | Escapes all style delimiters in a string |
strip_styling(s) | Removes all style tags, returning plain content |
cursor_to_content(s, pos) | Converts cursor pos to content character index |
content_to_cursor(s, pos, snap_to_content) | Converts content character index to cursor pos. When snap_to_content is true, the cursor skips structural positions like } and lands on the next visible character. |
🔗Processing order
When multiple tags are active on the same text, they are processed in this order:
- hide
- type (animation)
- fade (animation)
- scale (animation)
- transform
- wave
- pulse
- swing
- jitter
- gradient
- opacity
- color
- shadow
Later entries override earlier ones if they affect the same property (color, opacity).