Skip to main content

ctl_layout/
lib.rs

1//! Terminal widgets built on [`ctl_text`]: tables, records, rules, stacks,
2//! indents, and a way to swap one widget for another by width.
3//!
4//! Every widget implements [`Renderable`]. It first *measures*: the narrowest
5//! width it can be drawn at without breaking a word it keeps whole, and its
6//! natural width. Then it *renders* lines at the width it is given.
7//!
8//! A container measures its children, decides their widths, and renders each
9//! child at its own. The result is plain lines of styled spans with no escape
10//! sequence in them. Whatever writes the lines to a terminal adds the styling.
11//!
12//! The crate re-exports [`ctl_text`] and the four types every widget uses:
13//! [`Context`], [`Overflow`], [`Renderable`], and [`Text`].
14//!
15//! ## Quick start
16//!
17//! A [`Table`] gives each column the widest cell it holds, and shrinks the
18//! widest columns first when the width runs short. A column sets what
19//! happens to a cell that does not fit, and which edge its cells sit against:
20//!
21//! ```
22//! use ctl_layout::{Column, Context, Frame, Overflow, Renderable, Table};
23//!
24//! let table = Table::new([
25//!     Column::new("id"),
26//!     Column::new("title").overflow(Overflow::Ellipsis),
27//!     Column::new("age").numeric(),
28//! ])
29//! .frame(Frame::HeaderRule)
30//! .row(["job-0014", "Fix the layout of narrow tables", "3"])
31//! .row(["job-0015", "Document each public item", "120"]);
32//!
33//! let lines: Vec<String> = table
34//!     .render(&Context::new(40))
35//!     .iter()
36//!     .map(ToString::to_string)
37//!     .collect();
38//! assert_eq!(
39//!     lines,
40//!     [
41//!         "id        title                      age",
42//!         "────────────────────────────────────────",
43//!         "job-0014  Fix the layout of narrow…    3",
44//!         "job-0015  Document each public item  120",
45//!     ]
46//! );
47//! ```
48//!
49//! A [`Record`] draws labelled values, a [`Rule`] draws a line with an optional
50//! title, a [`Stack`] puts widgets one above the other, and an [`Indent`] moves
51//! one right. A stack and an indent take a widget by value or by reference:
52//!
53//! ```
54//! use ctl_layout::{Context, Indent, Record, Renderable, Rule, Stack, Text};
55//!
56//! let screen = Stack::new()
57//!     .spacing(1)
58//!     .push(Rule::titled(Text::plain("Release")))
59//!     .push(Indent::new(
60//!         2,
61//!         Record::new()
62//!             .row("version", "1.4.0")
63//!             .row("channel", "stable"),
64//!     ));
65//!
66//! let lines: Vec<String> = screen
67//!     .render(&Context::new(24))
68//!     .iter()
69//!     .map(ToString::to_string)
70//!     .collect();
71//! assert_eq!(
72//!     lines,
73//!     [
74//!         "── Release ─────────────",
75//!         "",
76//!         "  version  1.4.0",
77//!         "  channel  stable",
78//!     ]
79//! );
80//! ```
81//!
82//! A [`Breakpoint`] draws [`Definitions`] where a table no longer fits, so a
83//! narrow terminal gets one line of flags over an indented description:
84//!
85//! ```
86//! use ctl_layout::{Breakpoint, Column, Context, Definitions, Frame, Renderable, Table};
87//!
88//! let flags = Breakpoint::new(
89//!     40,
90//!     Definitions::new()
91//!         .entry(["-v", "--verbose"], ["Print more detail"])
92//!         .entry(["-q", "--quiet"], ["Print nothing on success"]),
93//!     Table::new([Column::untitled(), Column::untitled(), Column::untitled()])
94//!         .frame(Frame::Plain)
95//!         .row(["-v", "--verbose", "Print more detail"])
96//!         .row(["-q", "--quiet", "Print nothing on success"]),
97//! );
98//!
99//! let at = |width| -> Vec<String> {
100//!     flags
101//!         .render(&Context::new(width))
102//!         .iter()
103//!         .map(ToString::to_string)
104//!         .collect()
105//! };
106//! assert_eq!(
107//!     at(50),
108//!     [
109//!         "-v  --verbose  Print more detail",
110//!         "-q  --quiet    Print nothing on success",
111//!     ]
112//! );
113//! assert_eq!(
114//!     at(30),
115//!     [
116//!         "  -v --verbose",
117//!         "    Print more detail",
118//!         "  -q --quiet",
119//!         "    Print nothing on success",
120//!     ]
121//! );
122//! ```
123//!
124//! A widget of your own sits in a [`Stack`] beside these once it implements
125//! [`Renderable`]. Its [`Measurement`](ctl_text::Measurement) and
126//! [`Line`](ctl_text::Line) types come through
127//! [`ctl_layout::ctl_text`](ctl_text).
128//!
129//! ## Widgets
130//!
131//! - [`Table`]: rows under column headers. A [`Column`] sets the header, the
132//!   overflow policy, and how its cells are aligned ([`Align`]). A [`Frame`]
133//!   and a [`RowSeparation`] set the rules between them.
134//! - [`Record`]: labelled values, drawn in a [`RecordStyle`].
135//! - [`Definitions`]: entries as a line of labels over indented description
136//!   lines, the narrow form of a table.
137//! - [`Breakpoint`]: one widget below a width and another from it up.
138//! - [`Rule`]: a horizontal line with an optional title.
139//! - [`Stack`]: widgets one above the other.
140//! - [`Indent`]: a widget moved right.
141//!
142//! ## Cargo features
143//!
144//! This crate has no optional features.
145
146mod align;
147mod breakpoint;
148mod column;
149mod definitions;
150mod frame;
151mod glyphs;
152mod indent;
153mod lines;
154mod record;
155mod rule;
156mod stack;
157mod table;
158mod widths;
159
160pub use align::Align;
161pub use breakpoint::Breakpoint;
162pub use column::Column;
163#[doc(no_inline)]
164pub use ctl_text;
165pub use ctl_text::{Context, Overflow, Renderable, Text};
166pub use definitions::Definitions;
167pub use frame::{Frame, RecordStyle, RowSeparation};
168pub use indent::Indent;
169pub use record::Record;
170pub use rule::Rule;
171pub use stack::Stack;
172pub use table::Table;
173
174/// The README's examples, compiled and run as doctests.
175#[doc = include_str!("../README.md")]
176#[cfg(doctest)]
177pub struct ReadmeDoctests;