Skip to main content

Renderable

Trait Renderable 

Source
pub trait Renderable {
    // Required method
    fn render(&self, max_width: u16) -> Rendered;

    // Provided methods
    fn print(&self) -> Result<()> { ... }
    fn print_to<W: Write>(&self, writer: &mut W) -> Result<()> { ... }
}
Expand description

Anything that can be laid out into a Rendered and printed.

Required Methods§

Source

fn render(&self, max_width: u16) -> Rendered

Lays the value out for a terminal at most max_width columns wide.

Provided Methods§

Source

fn print(&self) -> Result<()>

Renders at the current terminal width and prints to standard output.

§Errors

Returns crate::SparcliError::Io if writing to stdout fails.

Examples found in repository?
examples/output_gallery.rs (line 35)
33fn section(title: &str) -> Result<()> {
34    println!();
35    Rule::with_title(title).align(Align::Left).print()?;
36    println!();
37    Ok(())
38}
39
40/// Styled spans, attributes, an OSC-8 hyperlink and optional markup.
41fn styled_text() -> Result<()> {
42    Rule::with_title("sparcli output gallery").print()?;
43    println!();
44    Rendered::new(vec![Line::new(vec![
45        Span::styled("bold ", Style::new().bold()),
46        Span::styled("dim ", Style::new().dim()),
47        Span::styled("italic ", Style::new().italic()),
48        Span::styled("red ", Style::new().fg(Color::Red)),
49        Span::styled("on-blue ", Style::new().bg(Color::Blue)),
50        Span::raw("link: "),
51        Span::raw("sparcli").link("https://example.com/sparcli"),
52    ])])
53    .print()?;
54
55    #[cfg(feature = "markup")]
56    markup_println("[bold green]markup[/]: [#ff8800]orange[/] and `code`")?;
57    Ok(())
58}
59
60/// Rules in all three alignments.
61fn sections() -> Result<()> {
62    section("Rules")?;
63    Rule::with_title("left")
64        .align(Align::Left)
65        .border(BorderType::Single)
66        .print()?;
67    Rule::with_title("center").align(Align::Center).print()?;
68    Rule::with_title("right")
69        .align(Align::Right)
70        .border(BorderType::Thick)
71        .print()?;
72    Ok(())
73}
74
75/// All five alert kinds.
76fn alerts() -> Result<()> {
77    section("Alerts")?;
78    Alert::info("Informational message.").print()?;
79    Alert::debug("Diagnostic detail.").print()?;
80    Alert::success("Everything compiled.").print()?;
81    Alert::warning("Low on disk space.").print()?;
82    Alert::error("Connection refused.").print()?;
83    Ok(())
84}
85
86/// Panels: borders, title/subtitle, fill and centered content.
87fn panels() -> Result<()> {
88    section("Panels")?;
89    Panel::new("Rounded border (default).").print()?;
90    Panel::new("Double border.")
91        .border(BorderType::Double)
92        .print()?;
93    Panel::new("Centered, filled, fixed width.")
94        .border(BorderType::Thick)
95        .title(Title::new("Title"))
96        .subtitle(Title::new("subtitle"))
97        .content_align(Align::Center)
98        .fill(Style::new().bg(Color::Indexed(236)))
99        .width(40)
100        .print()?;
101    Ok(())
102}
103
104/// Tables: colspan + striping, and footer + a wrapping column.
105fn tables() -> Result<()> {
106    section("Tables")?;
107    Table::new()
108        .title("Servers")
109        .columns(["Name", "Region", "Status"])
110        .row(["web-1", "eu-west", "online"])
111        .row(["db-1", "eu-west", "online"])
112        .row([Cell::new("maintenance window")
113            .colspan(3)
114            .align(Align::Center)])
115        .striped(true)
116        .print()?;
117    println!();
118    Table::new()
119        .columns([
120            Column::new("Item").align(Align::Left),
121            Column::new("Note").wrap().max_width(24),
122        ])
123        .row([
124            "alpha",
125            "a long note that wraps across several lines nicely",
126        ])
127        .row(["beta", "short note"])
128        .footer_row([Cell::new("2 items").colspan(2).align(Align::Right)])
129        .border(BorderType::Ascii)
130        .print()?;
131    Ok(())
132}
133
134/// Lists in several marker styles, plus a tree.
135fn lists_and_trees() -> Result<()> {
136    section("Lists & trees")?;
137    List::ordered(Marker::Number)
138        .item("First step")
139        .item_with("Second step", List::new().item("detail a").item("detail b"))
140        .item("Third step")
141        .print()?;
142    println!();
143    List::ordered(Marker::AlphaLower)
144        .item("alpha")
145        .item("beta")
146        .print()?;
147    List::ordered(Marker::RomanUpper)
148        .item("one")
149        .item("two")
150        .print()?;
151    println!();
152    Tree::new()
153        .node(
154            TreeNode::new("project")
155                .child(TreeNode::new("src").child(TreeNode::new("main.rs")))
156                .child(TreeNode::new("Cargo.toml")),
157        )
158        .print()?;
159    Ok(())
160}
161
162/// Key-value list and inline badges.
163fn key_values_and_badges() -> Result<()> {
164    section("Key-value & badges")?;
165    KeyValue::new()
166        .add("Version", "0.1.0")
167        .add("License", "MIT")
168        .print()?;
169    println!();
170    Rendered::new(vec![Line::new(vec![
171        Badge::new("PASS")
172            .style(Style::new().fg(Color::Green).bold())
173            .span(),
174        Span::raw(" "),
175        Badge::new("WARN")
176            .style(Style::new().fg(Color::Yellow).bold())
177            .span(),
178        Span::raw(" "),
179        Badge::new("v0.1").caps("(", ")").span(),
180    ])])
181    .print()?;
182    Ok(())
183}
184
185/// Progress bars in every style, a threshold bar, and a static spinner frame.
186fn progress_and_spinner() -> Result<()> {
187    section("Progress & spinner")?;
188    let styles = [
189        ("block", ProgressStyle::Block),
190        ("ascii", ProgressStyle::Ascii),
191        ("line", ProgressStyle::Line),
192        ("shaded", ProgressStyle::Shaded),
193    ];
194    for (label, style) in styles {
195        ProgressBar::new()
196            .style(style)
197            .label(label)
198            .width(20)
199            .bar(6.0, 10.0)
200            .print()?;
201    }
202    let thresholds = Thresholds {
203        mid: 0.5,
204        high: 0.8,
205        low_color: Color::Green,
206        mid_color: Color::Yellow,
207        high_color: Color::Red,
208    };
209    ProgressBar::new()
210        .thresholds(thresholds)
211        .label("load")
212        .width(20)
213        .bar(9.0, 10.0)
214        .print()?;
215    Spinner::new("spinner (static frame)").frame().print()?;
216    Ok(())
217}
218
219/// A colored diff and a two-column layout.
220fn diff_and_columns() -> Result<()> {
221    section("Diff & columns")?;
222    Diff::new(
223        "line one\nline two\nline three",
224        "line one\nline 2\nline three",
225    )
226    .print()?;
227    println!();
228    let left = Panel::new("left").render(20);
229    let right = Panel::new("right").render(20);
230    Columns::new()
231        .add_rendered(left)
232        .add_rendered(right)
233        .gap(3)
234        .separator(BorderType::Single)
235        .print()?;
236    Ok(())
237}
238
239/// Composition helpers: align, pad and vstack.
240fn composition() -> Result<()> {
241    section("Composition (align / pad / vstack)")?;
242    let block = Rendered::new(vec![Line::raw("aligned right")]);
243    align(&block, 30, Align::Right).print()?;
244    println!();
245    pad(&Rendered::new(vec![Line::raw("padded")]), Edges::all(1)).print()?;
246    println!();
247    let a = Rendered::new(vec![Line::raw("first block")]);
248    let b = Rendered::new(vec![Line::raw("second block")]);
249    vstack(&[a, b], 1).print()?;
250    Ok(())
251}
More examples
Hide additional examples
examples/prompt-readme.rs (line 23)
18fn main() -> Result<()> {
19    println!();
20    let board = dashboard();
21    hero(board.width() as u16)?;
22    println!();
23    board.print()?;
24    Ok(())
25}
26
27/// A blank one-line spacer for `vstack`.
28fn blank() -> Rendered {
29    Rendered::new(vec![Line::default()])
30}
31
32/// The top hero panel, sized to match the dashboard width.
33fn hero(width: u16) -> Result<()> {
34    let theme = theme();
35    let body = Text::new(vec![
36        Line::raw("Interactive prompts - confirm · select · text · password ·"),
37        Line::raw("number · textarea · fuzzy · date."),
38    ]);
39    Panel::new(body)
40        .border(BorderType::Rounded)
41        .border_style(Style::new().fg(theme.accent))
42        .title(
43            Title::new(" sparcli · input widgets ")
44                .align(Align::Center)
45                .style(theme.title),
46        )
47        .content_align(Align::Center)
48        .width(width)
49        .print()
50}
examples/output-readme.rs (line 59)
29fn hero() -> Result<()> {
30    let cyan = Style::new().fg(Color::Cyan).bold();
31    let on_magenta = Style::new().fg(Color::Black).bg(Color::Magenta).bold();
32    let yellow = Style::new().fg(Color::Yellow).bold();
33    let body = Text::new(vec![
34        Line::new(vec![
35            Span::raw("A native Rust library for "),
36            Span::styled("styled terminal output", cyan),
37            Span::raw(" and "),
38            Span::styled("input", on_magenta),
39            Span::raw(" - panels, tables, trees, lists,"),
40        ]),
41        Line::new(vec![
42            Span::raw("key-value pairs, badges, rules, "),
43            Span::styled("columns", cyan),
44            Span::raw(" and progress bars, with Rich-compatible "),
45            Span::styled("[markup]", yellow),
46            Span::raw("."),
47        ]),
48    ]);
49    Panel::new(body)
50        .border(BorderType::Rounded)
51        .border_style(Style::new().fg(Color::Cyan))
52        .title(
53            Title::new(" sparcli ")
54                .align(Align::Center)
55                .style(Style::new().fg(Color::Cyan).bold()),
56        )
57        .content_align(Align::Center)
58        .width(96)
59        .print()
60}
61
62/// The three-column dashboard row.
63fn dashboard() -> Result<()> {
64    Columns::new()
65        .add_rendered(overview_table())
66        .add_rendered(middle_column())
67        .add_rendered(right_column())
68        .gap(3)
69        .separator(BorderType::Single)
70        .print()
71}
72
73/// Left column: the striped, row-separated "Overview" table.
74fn overview_table() -> Rendered {
75    let status = |text: &str, color: Color| {
76        Cell::new(Text::styled(text, Style::new().fg(color).bold()))
77    };
78    Table::new()
79        .title("Overview")
80        .title_style(Style::new().fg(Color::Magenta).bold())
81        .border(BorderType::Rounded)
82        .border_style(Style::new().fg(Color::Magenta))
83        .header_style(Style::new().fg(Color::Cyan).bold())
84        .striped(true)
85        .stripe_style(Style::new().bg(Color::Rgb(40, 40, 60)))
86        .row_separators(true)
87        .columns([
88            Column::new("Service"),
89            Column::new("Status").align(Align::Center),
90            Column::new("Uptime").align(Align::Right),
91        ])
92        .row([
93            Cell::new("api-gateway"),
94            status("● OK", Color::Green),
95            Cell::new("99.98%"),
96        ])
97        .row([
98            Cell::new("auth"),
99            status("● OK", Color::Green),
100            Cell::new("99.91%"),
101        ])
102        .row([
103            Cell::new("billing"),
104            status("● WARN", Color::Yellow),
105            Cell::new("97.40%"),
106        ])
107        .row([
108            Cell::new("scheduler"),
109            status("● FAIL", Color::Red),
110            Cell::new("82.10%"),
111        ])
112        .render(40)
113}
114
115/// Middle column: a numbered list above a tree beside titled rules.
116fn middle_column() -> Rendered {
117    let list = List::ordered(Marker::Number)
118        .marker_style(Style::new().fg(Color::Yellow).bold())
119        .item("Compose widgets side by side")
120        .item("Capture, pad, and align them")
121        .item("Style with Rich-style markup")
122        .item("Render to any UTF-8 terminal")
123        .render(36);
124
125    let tree = Tree::new()
126        .dashes(2)
127        .connector_style(Style::new().fg(Color::Cyan))
128        .node(
129            TreeNode::new("project/")
130                .child(
131                    TreeNode::new("api/")
132                        .child(TreeNode::new("routes.c"))
133                        .child(TreeNode::new("auth.c")),
134                )
135                .child(TreeNode::new("worker.c"))
136                .child(TreeNode::new("store.c")),
137        )
138        .render(18);
139
140    let rules = vstack(
141        &[
142            rule("Pipeline", BorderType::Single, Color::Magenta),
143            blank(),
144            rule("Workers", BorderType::Double, Color::Green),
145            blank(),
146            rule("Storage", BorderType::Thick, Color::Yellow),
147            blank(),
148        ],
149        0,
150    );
151
152    let tree_and_rules = Columns::new()
153        .add_rendered(tree)
154        .add_rendered(rules)
155        .gap(2)
156        .render(40);
157
158    vstack(&[list, blank(), tree_and_rules], 0)
159}
160
161/// One titled rule with a colored title and line.
162fn rule(title: &str, border: BorderType, color: Color) -> Rendered {
163    Rule::with_title(Text::styled(title, Style::new().fg(color).bold()))
164        .border(border)
165        .style(Style::new().fg(color))
166        .width(20)
167        .render(20)
168}
169
170/// Right column: key-value pairs, badges and a staggered columns demo.
171fn right_column() -> Rendered {
172    let kv = KeyValue::new()
173        .key_style(Style::new().fg(Color::Cyan).bold())
174        .value_style(Style::new())
175        .add("host", "localhost")
176        .add("port", "8080")
177        .add("scheme", "https")
178        .add("timeout", "30s")
179        .render(24);
180
181    let badge = |text: &str, color: Color| {
182        Badge::new(text)
183            .pad(1)
184            .style(Style::new().fg(Color::Black).bg(color).bold())
185            .span()
186    };
187    let badges = Rendered::new(vec![
188        Line::new(vec![
189            badge("DONE", Color::Green),
190            Span::raw(" "),
191            badge("INFO", Color::LightBlue),
192        ]),
193        Line::default(),
194        Line::new(vec![
195            badge("WARN", Color::Yellow),
196            Span::raw(" "),
197            badge("FAIL", Color::Red),
198        ]),
199    ]);
200
201    let label = |text: &str| {
202        Rendered::new(vec![Line::styled(
203            text,
204            Style::new().fg(Color::Cyan).bold(),
205        )])
206    };
207    let staggered = Columns::new()
208        .add_rendered(label("Col 1"))
209        .add_rendered(vstack(&[blank(), label("Col 2")], 0))
210        .add_rendered(vstack(&[blank(), blank(), label("Col 3")], 0))
211        .gap(2)
212        .separator(BorderType::Single)
213        .render(40);
214
215    vstack(&[kv, blank(), badges, blank(), staggered], 0)
216}
217
218/// The bottom progress bar.
219fn progress() -> Result<()> {
220    ProgressBar::new()
221        .label("Building")
222        .fill_color(Color::Green)
223        .caps("[", "]")
224        .width(29)
225        .show_value(true)
226        .bar(92.0, 100.0)
227        .print()
228}
examples/prompts.rs (line 92)
28fn main() -> sparcli::Result<()> {
29    footer_hint()?;
30
31    let name = get!(
32        TextInput::new("Your name?")
33            .placeholder("e.g. Alice")
34            .validate(non_empty())
35            .run()?
36    );
37
38    let _username = get!(
39        TextInput::new("Username?")
40            .char_filter(alnum())
41            .max_chars(16)
42            .suggestions(["alice", "albert", "bob", "carol"])
43            .history(["alice", "bob"])
44            .run()?
45    );
46
47    let _password = get!(PasswordInput::new("Password?").mask("•").run()?);
48
49    let age = get!(
50        NumberInput::new("Age? (try `= 20 + 2`)")
51            .range(0.0, 130.0)
52            .calculator()
53            .run()?
54    );
55
56    let _bio = get!(Textarea::new("Short bio (Ctrl-D to submit):").run()?);
57
58    let color = get!(
59        Select::new("Favorite color?")
60            .options(["red", "green", "blue"])
61            .run()?
62    );
63
64    let toppings = get!(
65        Select::new("Pick toppings (Space to toggle):")
66            .options(["cheese", "mushroom", "olive", "onion"])
67            .multi()
68            .run_multi()?
69    );
70
71    let _fruit = get!(pick_fruit()?);
72
73    let date = get!(DatePicker::new("Pick a date:").run()?);
74
75    let confirmed = matches!(
76        Confirm::new("Save everything?")
77            .default_yes()
78            .labels("Save", "Discard")
79            .run()?,
80        Outcome::Submitted(true)
81    );
82
83    if confirmed {
84        Alert::success(format!(
85            "Saved {name}, age {age:.0}, color #{color}, {} toppings, \
86             date {}-{:02}-{:02}.",
87            toppings.len(),
88            date.year,
89            date.month,
90            date.day,
91        ))
92        .print()?;
93    } else {
94        cancelled()?;
95    }
96    Ok(())
97}
98
99/// Runs the fuzzy picker when the feature is enabled.
100#[cfg(feature = "fuzzy")]
101fn pick_fruit() -> sparcli::Result<Outcome<usize>> {
102    FuzzySelect::new("Find a fruit:")
103        .options(["apple", "apricot", "banana", "cherry", "grape"])
104        .run()
105}
106
107/// Stand-in that submits a default when the `fuzzy` feature is disabled.
108#[cfg(not(feature = "fuzzy"))]
109fn pick_fruit() -> sparcli::Result<Outcome<usize>> {
110    Ok(Outcome::Submitted(0))
111}
112
113/// Prints a footer-style key hint line above the prompts.
114fn footer_hint() -> sparcli::Result<()> {
115    let shortcuts = [
116        Shortcut::new(KeyPress::new(KeyCode::Enter), 1, "submit"),
117        Shortcut::new(KeyPress::new(KeyCode::Esc), 2, "cancel"),
118    ];
119    Rendered::new(vec![shortcut::hint_line(&shortcuts)]).print()
120}
121
122/// Prints a cancellation notice.
123fn cancelled() -> sparcli::Result<()> {
124    Alert::warning("Cancelled.").print()
125}
Source

fn print_to<W: Write>(&self, writer: &mut W) -> Result<()>

Renders at the current terminal width and writes to writer.

Useful for redirecting output to a file or in-memory buffer.

§Errors

Returns crate::SparcliError::Io if writing fails.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§