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§
Provided Methods§
Sourcefn print(&self) -> Result<()>
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
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}Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".