Display Widgets (Read-Only)
Text
Renders styled text.
| Prop | Type | Description |
|---|---|---|
content | impl Into<String> | Constructor - text content |
spans | impl IntoIterator<Item = Span> | Construct from styled spans |
from_ansi | &str | Construct from ANSI-escaped string (SGR sequences → styled spans) |
style | Style | Text style |
overflow | Overflow | Clip, Ellipsis, Wrap |
width | Length | Width |
height | Length | Height |
Text::new("Hello, World!")
.style(Style::new().fg(Color::Cyan).bold())
.overflow(Overflow::Ellipsis)// Render ANSI-styled output (ls --color, compiler errors, git diff, etc.)
Text::from_ansi("\x1b[31merror\x1b[0m: file not found")DocumentView
Read-only rich document renderer with pluggable formatting.
Use it for markdown previews, formatted logs, and custom read-only views where display content differs from source text.
| Prop | Type | Description |
|---|---|---|
value | impl Into<Arc<str>> | Constructor - source text |
content_type | Arc<str> | Optional formatter hint ("markdown", etc.) |
formatter | impl ContentFormatter | Custom formatting strategy |
width | Length | Width |
height | Length | Height |
wrap | bool | Word-wrap long lines (default: true) |
border | bool | Show/hide outer border (default: true) |
border_style | BorderStyle | Border glyph variant |
hover_border_style | BorderStyle | Border variant when hovered |
padding | impl Into<Padding> | Inner padding |
table_wrap | bool | Wrap table cell text within column width |
table_width_mode | DocumentTableWidthMode | Content (natural) or Fill (stretch to viewport) |
table_outer_frame | bool | Show/hide outer table frame |
table_inner_frame | bool | Show/hide inner table separators |
table_cell_padding | u16 | Horizontal padding inside each table cell |
table_border_variant | BorderStyle | Table border glyph variant (Plain, Rounded, Double, etc.) |
table_border_style | Style | Style for table borders (color/emphasis) |
line_numbers | bool | Source-mapped line-number gutter |
line_number_mode | DocumentLineNumberMode | Visual (visual row index) or Source (source line mapping) |
min_line_number_width | u8 | Minimum line-number gutter digit width |
line_number_separator | bool | Show/hide built-in line-number separator (" │ ", default: true) |
line_number_content_gap | u16 | Empty cells between built-in line numbers and content |
line_number_style | Style | Style override for built-in line-number gutter text |
gutter_inset | u16 | Empty cells before the gutter / line numbers |
style | Style | Base style |
hover_style | Style | Hover style |
focus_style | Style | Focus chrome style |
focus_content_style | Style | Text content style when focused |
selection_style | Style | Selection style (reserved for selection mode) |
highlight_full_width | bool | Extend per-line background highlights across full content width |
doc_styles | DocumentStyles | Element styles (heading/code/link/table/hr/etc.) |
code_block_style | Style | Shortcut - sets doc_styles.code_block_style |
scroll_offset | usize | Controlled vertical scroll offset |
scroll_to_source_line | usize | Scroll-sync target source line |
scrollbar | bool | Show/hide vertical scrollbar |
scrollbar_config | ScrollbarConfig | Full scrollbar configuration (variant, gap, thumb, thumb styles) |
h_scrollbar | bool | Show/hide horizontal scrollbar (only when wrap is false) |
scroll_wheel | bool | Enable/disable mouse wheel scrolling (default: true) |
focusable | bool | Participate in focus traversal; mouse selection and Ctrl+C copy still work when false |
on_scroll | Callback<ScrollEvent> | Scroll callback |
on_click | Callback<DocumentClickEvent> | Click callback with source-line mapping |
on_select | Callback<DocumentSelectEvent> | Text selection callback |
on_key | KeyHandler | Focused keyboard handler |
shared_selection_id | Arc<str> | Group id for cross-DocumentView selection/copy within the same ScrollView |
triple_click_mode | TripleClickSelectionMode | Triple-click selects a visual line or paragraph |
DocumentView ships with PlainFormatter by default.
For inline/message-style read-only blocks, wrapped DocumentView now implicitly behaves like height: Length::Auto when all of these are true: default height: Flex(1), wrap: true, scrollbar: false, h_scrollbar: false, and focusable: false. Set .height(...) explicitly when you want viewport-style behavior instead.
When using DocumentView::markdown(), default heading/link/code/table styling is derived from Theme::document. Use .doc_styles(...) or a custom MarkdownFormatter::styles(...) only when you want local overrides.
With feature markdown, you can use MarkdownFormatter:
DocumentView::new("# Hello\n\n| A | B |\n|---|---|\n| 1 | 2 |")
.markdown()
.line_numbers(true)
.wrap(true)
// Compact mode - collapses blank lines between blocks
DocumentView::new(text)
.markdown_compact(true)Keyboard scrolling (when focused): arrows, j/k, PageUp/PageDown, Home/End.
Table drag-selection supports rectangular selection by row/column and copies as TSV.
When sibling DocumentView widgets under the same ScrollView share shared_selection_id, linear drag selection can continue across widget boundaries and shared copy concatenates text in visual order with newline separators between document boundaries.
Custom markdown styles
DocumentStyles controls per-element colors. Default values come from Theme::document automatically - only set this when you want widget-local overrides.
// Via DocumentView::doc_styles - applies to any formatter
DocumentView::new(text)
.markdown()
.doc_styles(DocumentStyles {
heading_styles: [
Style::new().bold().fg(Color::Cyan), // h1
Style::new().bold().fg(Color::Blue), // h2
Style::new().bold().fg(Color::Green), // h3
Style::new().bold(), // h4
Style::new().bold(), // h5
Style::new().bold().dim(), // h6
],
link_style: Style::new().fg(Color::Blue).underline(),
code_inline_style: Style::new().fg(Color::Green),
code_block_style: Style::new().bg(Color::rgb(0x1E, 0x1E, 0x1E)),
emphasis_style: Style::new().italic(),
strong_style: Style::new().bold(),
strikethrough_style: Style::new().strikethrough(),
blockquote_bar_style: Style::new().fg(Color::DarkGray),
table_border_style: Style::new().fg(Color::DarkGray),
table_header_style: Style::new().bold(),
hr_style: Style::new().fg(Color::DarkGray).dim(),
});
// Via MarkdownFormatter::styles - same effect, explicit formatter path
DocumentView::new(text)
.formatter(MarkdownFormatter::default().styles(DocumentStyles {
strong_style: Style::new().bold().fg(Color::Yellow),
..DocumentStyles::default() // theme fills in the rest automatically
}));Syntax highlighting in code blocks (requires feature syntax-syntect)
DocumentView::new(text)
.markdown()
.code_syntax_strategy(SyntectStrategy::default().default_theme("One Dark (Atom)"))
// .markdown() already sets this default theme; override here to use a different oneAsciiCanvas
Renders ASCII art as text lines, cell grids, or multi-frame sprite sheets.
Constructors
// Line-based content
AsciiCanvas::new(["Line 1", "Line 2"])
// Cell grid
AsciiCanvas::from_cells(width, height, cells)
// Blank grid for programmatic fill
AsciiCanvas::blank(width, height)
// Generated grid
AsciiCanvas::with_cell_fn(width, height, |x, y| AsciiCanvasCell { ch, fg, bg })
// Multi-frame sprite sheet
AsciiCanvas::from_sequence(Arc::new(frame_sequence))Frame Sequence API
let seq = Arc::new(FrameSequence::from_json(&json_str).unwrap());
let canvas = AsciiCanvas::from_sequence(seq.clone())
.frame(0) // Select frame by index
// or: .frame_by_tag("key", "value") // Select frame by tagFrameSequence::from_json() parses ASCII Motion export format. Supports foreground and background color maps.
Color Remapping
// Unified map (both fg and bg)
let colors = seq.collect_colors(); // All unique colors, fg+bg merged
let canvas = AsciiCanvas::from_sequence(seq)
.color_map(vec![
(colors[0], theme.highlight.fg.unwrap_or(Color::White)),
(colors[1], Color::hex("#3A3A3A")),
]);
// Per-channel maps (when same hex appears in both fg and bg)
let fg_colors = seq.collect_fg_colors(); // Unique foreground colors
let bg_colors = seq.collect_bg_colors(); // Unique background colors
let canvas = AsciiCanvas::from_sequence(seq)
.fg_color_map(vec![(fg_colors[0], Color::White)])
.bg_color_map(vec![(bg_colors[0], Color::Black)]);Per-channel maps take precedence over the unified map for their respective channel.
Props
| Prop | Type | Description |
|---|---|---|
lines | Vec<String> | Constructor - line-based content |
style | Style | Base style |
background | Style | Background-only style |
color_map | Vec<(Color, Color)> | Unified fg+bg remap |
fg_color_map | Vec<(Color, Color)> | Foreground-only remap |
bg_color_map | Vec<(Color, Color)> | Background-only remap |
grid_size | (u16, u16) | Grid dimensions (must match cells count) |
width | Length | Width |
height | Length | Height |
BigText (requires feature big-text)
Large text rendered with ASCII or pixel fonts.
| Prop | Type | Description |
|---|---|---|
text | impl Into<RichText> | Text content or Vec<Span> for multicolor |
font | BigTextFont | Font choice (see below) |
style | Style | Base style (per-span styles override this) |
shadow | Shadow | Shadow configuration |
with_shadow | Style | Quick shadow with given style |
custom_figlet | String | Custom .flf FIGlet font content |
width | Length | Width |
height | Length | Height |
FIGlet fonts: Standard, Slant, Bloody, Colossal, Roman, SubZero, Poison, Nancyj, SmallPoison, DosRebel, AnsiShadow, Small, CustomFiglet
Pixel fonts: Pixel (8x8 half blocks), PixelBold, Quadrant (2×2 block mapping)
BigText::new("Hello")
.font(BigTextFont::AnsiShadow)
.style(Style::new().fg(Color::Cyan))
// Multicolor via spans
BigText::new(vec![
Span::new("open").fg(Color::Cyan),
Span::new("code").fg(Color::White),
])
.font(BigTextFont::Standard)FIGlet smushing does not cross span boundaries in multicolor mode.
Image (requires feature image)
Protocol-aware image rendering.
| Prop | Type | Description |
|---|---|---|
src | impl Into<String> | Constructor - image file path |
bytes | Arc<[u8]> | In-memory image (use Image::from_bytes(...)) |
fit | ImageFit | Contain (default), Crop, Scale |
protocol | ImageProtocol | Auto, Kitty, Iterm2, Sixel, Halfblocks |
style | Style | Container style |
alt | String | Alt text shown when protocol fails |
playback | ImagePlayback | Playing, Paused |
repeat | ImageRepeat | Loop, Once |
speed_percent | u32 | Animation speed percentage |
width | Length | Width |
height | Length | Height |
Image::new("logo.png")
.fit(ImageFit::Contain)
.alt("Company Logo")
// From memory
let bytes: Arc<[u8]> = load_image_bytes();
Image::from_bytes(bytes)
.protocol(ImageProtocol::Auto)Animation (GIF, animated WebP, APNG): advance automatically.
- Small GIFs are preloaded; large GIFs use a bounded worker channel.
- Controls via
playback,repeat,speed_percent.
Environment knobs:
| Variable | Default | Description |
|---|---|---|
TUI_LIPAN_IMAGE_MAX_FPS | 30 | Frame rate cap |
TUI_LIPAN_IMAGE_MAX_CATCHUP_MS | 100 | Frame catch-up window |
TUI_LIPAN_IMAGE_AUTO_ANIM_HALF_BLOCKS | false | Allow halfblocks for animations |
TUI_LIPAN_IMAGE_ENCODE_WORKERS | 1 | Async encoding workers (1–2) |
TUI_LIPAN_IMAGE_GIF_PRELOAD | true | Eager GIF preloading |
TUI_LIPAN_IMAGE_GIF_PRELOAD_MAX_BYTES | 262144 | Preload size cap |
TUI_LIPAN_IMAGE_GIF_PRELOAD_MAX_FRAMES | 24 | Preload frame cap |
TUI_LIPAN_IMAGE_GIF_PRELOAD_BUDGET_MS | 16 | Preload time cap |
TUI_LIPAN_IMAGE_GIF_WORKER_QUEUE | 2 | Worker queue size (1–4) |
TUI_LIPAN_IMAGE_RESIZE_PAUSE_MS | 180 | Pause rendering during resize |
TUI_LIPAN_IMAGE_LAYOUT_STABILIZE_MS | 120 | Pause while layout changes |
Sparkline
Minimal inline chart for time-series data.
| Prop | Type | Description |
|---|---|---|
data | Vec<u64> | Constructor - data points |
variant | SparklineVariant | Bars (default), Braille, Line |
min | Option<u64> | Data minimum (auto if None) |
max | Option<u64> | Data maximum (auto if None) |
chart_height | u16 | Multi-row height for Bars/Braille/Line |
mirror_x | bool | Reverse sample order (time axis) |
mirror_y | bool | Flip vertical direction |
max_points | Option<usize> | Cap rendered width (enables downsampling) |
aggregation | SparklineAggregation | Average, Min, Max, First, Last |
zero_policy | SparklineZeroPolicy | MinGlyph for baseline on zeros |
gradient | ColorGradient | Map values → RGB colors |
height_gradient | ColorGradient | Map row position → RGB colors |
gradient_range | GradientRange | Normalize gradient range |
style | Style | Base style |
rising_style | Style | Style for rising values |
falling_style | Style | Style for falling values |
overflow | Overflow | Default: Ellipsis; use ClipStart for live charts |
width | Length | Width |
height | Length | Height |
Sparkline::new(metrics.clone())
.variant(SparklineVariant::Braille)
.chart_height(4)
.overflow(Overflow::ClipStart) // Keep newest data visible
.gradient(ColorGradient::new(vec![
(0.0, Color::Green),
(1.0, Color::Red),
]))Chart
Multi-series chart with axes, legend, thresholds, and viewport windowing.
| Prop | Type | Description |
|---|---|---|
series | Vec<ChartSeries> | Data series |
x_axis | ChartAxis | X axis configuration |
y_axis | ChartAxis | Y axis configuration |
thresholds | Vec<ChartThreshold> | Horizontal threshold lines |
viewport_start | Option<usize> | Start sample index for zoom |
viewport_len | Option<usize> | Number of samples to show |
show_legend | bool | Show series legend |
show_grid | bool | Show background grid |
legend_style | Style | Legend style |
grid_style | Style | Grid style |
legend_separator | char | Legend entry separator |
border | bool | Draw border |
border_style | BorderStyle | Border appearance |
padding | impl Into<Padding> | Inner padding |
width | Length | Width |
height | Length | Height |
let series = ChartSeries::new("CPU", cpu_data)
.mode(ChartSeriesMode::Line)
.style(Style::new().fg(Color::Cyan));
Chart::new()
.series(vec![series])
.show_legend(true)
.show_grid(true)
.thresholds(vec![ChartThreshold::new(80.0).style(Style::new().fg(Color::Red))])Heatmap
2D matrix visualization with gradient-colored cells or glyphs.
| Prop | Type | Description |
|---|---|---|
data | Vec<Vec<f64>> | Constructor - 2D matrix of values |
row_labels | Vec<impl Into<Arc<str>>> | Labels on the left side |
column_labels | Vec<impl Into<Arc<str>>> | Labels on top |
gradient | ColorGradient | Color gradient for value mapping |
range | (f64, f64) | Explicit min/max for gradient normalization |
cell_mode | HeatmapCellMode | Background, Glyph(Arc<str>), or GlyphForeground(Arc<str>) |
cell_width | u16 | Character width per cell (default: 4) |
gap_x | u16 | Horizontal gap between cells in characters |
gap_y | u16 | Vertical gap between heatmap rows in lines |
legend_gap | u16 | Horizontal gap between legend markers/swatches |
legend_spacing | u16 | Vertical gap between the heatmap grid and legend |
legend_width | HeatmapLegendWidth | Legend alignment: grid width or full inner width |
show_values | bool | Display numeric values in cells |
show_legend | bool | Show gradient legend below |
style | Style | Base style |
label_style | Style | Row/column label style |
legend_style | Style | Legend style |
padding | impl Into<Padding> | Inner padding |
border | bool | Draw border |
border_style | BorderStyle | Border appearance |
width | Length | Width |
height | Length | Height |
Use HeatmapCellMode::Glyph(...) when you want a repeated glyph texture with colored tile backgrounds, or HeatmapCellMode::GlyphForeground(...) when you want only the glyph colored and the background left untouched. Both glyph modes accept strings like " " as well as single characters. gap_x and gap_y work in all modes and make sparse glyph layouts much easier to read. legend_gap separates legend markers from each other, legend_spacing adds space between the heatmap grid and the legend itself, and legend_width(HeatmapLegendWidth::Full) lets the legend ignore the row-label gutter and stretch across the full inner width.
let data = vec![
vec![10.0, 25.0, 40.0, 55.0],
vec![20.0, 35.0, 50.0, 65.0],
vec![30.0, 45.0, 60.0, 75.0],
];
Heatmap::new(data)
.row_labels(["Low", "Med", "High"])
.column_labels(["Q1", "Q2", "Q3", "Q4"])
.gradient(ColorGradient::new(Color::Rgb(60, 179, 113), Color::Rgb(226, 82, 87)))
.range(0.0, 100.0)
.cell_mode(HeatmapCellMode::GlyphForeground(" ".into()))
.gap_x(1)
.gap_y(1)
.legend_gap(1)
.legend_spacing(1)
.legend_width(HeatmapLegendWidth::Full)
.show_legend(true)
.border(true)