Styling text
Every bit of text you draw carries a Style: foreground and background color,
attributes like bold and italic, an underline shape and color, and optionally an
OSC 8 hyperlink. You build a style with chained methods and hand it to whatever
paints the text.
Building a style
Style::new() (or Style::default()) starts empty: no color, no attributes, no
underline, and no link. Builder methods return an updated style, so you chain
only what you want.
use uncurses::color::Color;
use uncurses::style::{Style, UnderlineStyle};
let heading = Style::new()
.bold()
.fg(Color::BrightCyan);
let warning = Style::new()
.fg(Color::Rgb(255, 105, 180))
.bg(Color::Black)
.italic();
let spelling = Style::new()
.underline()
.underline_style(UnderlineStyle::Curly)
.underline_color(Color::BrightRed);Attributes include bold, faint, italic, underline, reverse,
strikethrough, conceal, blink, and rapid_blink. Underline shapes go
beyond a single line: double, curly, dotted, and dashed, and each can have an
underline color.
Colors
Colors come in three representations, and you can use them freely:
Style::new().fg(Color::Green); // 16-color
Style::new().fg(Color::Indexed(208)); // 256-color palette
Style::new().fg(Color::Rgb(255, 105, 180)); // 24-bit truecolor
You always specify the color you mean. When the screen renders to a terminal that cannot do truecolor, the renderer downsamples to the nearest supported color through its color profile; you do not branch on terminal capability yourself.
Painting styled text
On a Screen, TextBuffer, or any other TextSurface, pass the style as the
third argument to set_str:
use uncurses::text::TextSurface;
screen.set_str((2, 1), "Heading", heading);
screen.set_str((2, 3), "be careful", warning);set_str paints the text literally: the style applies to the whole run, and any
escape sequences in the string are drawn as visible characters, not interpreted.
Inline escapes with a Painter
Sometimes the string you want to draw already contains SGR or OSC 8 escapes,
perhaps from another program, a log line with ANSI codes, or a snippet you
assembled by hand. A Painter
interprets those inline escapes and turns them into cell styles, where the
default set_str would draw them literally.
Wrap any TextSurface, such as a Screen or TextBuffer, in a Painter and
paint through it:
use uncurses::style::Style;
use uncurses::text::{Painter, TextSurface};
Painter::new(&mut screen).set_str(
(2, 1),
"plain \x1b[1;32mbold green\x1b[0m back to plain",
Style::new(),
);The \x1b[1;32m and \x1b[0m are read as Painter walks the string, so
“bold green” lands bold and green while the rest stays plain. The Style you
pass is the base: inline escapes layer on top of it, winning where they set a
field and letting the base fill in the rest. An inline reset (\x1b[0m) clears
the inline state, so the cells after it fall back to your base rather than to
the terminal default. Had you passed Style::new().fg(Color::Blue) above,
“back to plain” would return blue, not colorless.
The painter keeps no style between calls: every set_str starts fresh from the
base you hand it, so calls never bleed into each other.
Hyperlinks
A Style can carry an OSC 8 hyperlink with Style::link. Any rendered cell
whose style has a link becomes clickable in terminals that support OSC 8.
let docs = Style::new()
.underline()
.fg(Color::BrightBlue)
.link("https://github.com/aymanbagabas/uncurses", "");
screen.set_str((2, 5), "uncurses on GitHub", docs);The second argument is the OSC 8 parameter string, usually left empty. A
nonempty value such as id=docs asks terminals that support it to treat
separate runs as parts of the same link. That can help when a link wraps across
lines; the exact presentation is terminal-dependent.
Styling plain output
Off the grid, a Style also implements Display: formatting it writes the
opener, which is any SGR sequence plus an OSC 8 start if the style carries a
link. The alternate form {style:#} writes that style’s closer, so styled
println! follows an open/close pattern with a single value:
let bold = Style::new().bold();
println!("{bold}important{bold:#}");# is Rust’s alternate format flag. On a Style it flips Display from
the opener to the closer, so {bold:#} means “close this style”.The opener is additive, so an empty style writes nothing. The closer only emits
what it needs: an SGR-only style closes with CSI m, while a linked style also
writes the OSC 8 terminator. The closer resets that span to defaults; it does
not restore an outer style or the terminal’s previous state.
This works with any io::Write too, since write! forwards Display:
write!(out, "{bold}important{bold:#}")?;See the styles example in examples/examples/styles.rs for a full tour of
attributes, underline shapes, colors, and a clickable hyperlink.