ctl_text/protocol.rs
1//! The protocol a widget follows: measure, then render.
2
3use std::num::NonZeroU16;
4
5use crate::cells::TAB_WIDTH;
6use crate::line::Line;
7
8/// The narrowest and widest a piece of content can usefully be, in cells.
9///
10/// The minimum is the widest unit the content will not break: a word under
11/// [`Overflow::KeepWords`](crate::Overflow::KeepWords), a cluster under
12/// [`Overflow::Fold`](crate::Overflow::Fold). The maximum is the width the
13/// content takes when nothing wraps it. The maximum is never below the
14/// minimum.
15#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
16pub struct Measurement {
17 minimum: u16,
18 maximum: u16,
19}
20
21impl Measurement {
22 /// A measurement from `minimum` to `maximum`. A `maximum` below
23 /// `minimum` is raised to it.
24 #[must_use]
25 pub fn new(minimum: u16, maximum: u16) -> Self {
26 Self {
27 minimum,
28 maximum: maximum.max(minimum),
29 }
30 }
31
32 /// A measurement of content that is always `width` cells wide.
33 #[must_use]
34 pub fn exact(width: u16) -> Self {
35 Self::new(width, width)
36 }
37
38 /// The narrowest width the content can be laid out in.
39 #[must_use]
40 pub fn minimum(self) -> u16 {
41 self.minimum
42 }
43
44 /// The width the content takes when nothing wraps it.
45 #[must_use]
46 pub fn maximum(self) -> u16 {
47 self.maximum
48 }
49}
50
51/// The characters a widget draws borders and rules with.
52#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
53#[non_exhaustive]
54pub enum BoxChars {
55 /// Unicode box-drawing characters.
56 #[default]
57 Unicode,
58 /// ASCII only: `+`, `-`, and `|`.
59 Ascii,
60}
61
62/// What a widget is told when it measures and renders: the width to fill, the
63/// tab stop spacing, and the box characters.
64///
65/// A context holds no colour or terminal fact. Styles are resolved before
66/// layout, and escape sequences are written after it.
67///
68/// It is `Clone` but not `Copy`, so a later release can add a field without
69/// breaking a caller that copies it. A widget takes `&Context`.
70///
71/// The `at_*` and `using_*` methods return a changed clone, so a container
72/// derives the context of a child from its own: `context.at_width(column)`.
73#[derive(Clone, Debug, Eq, Hash, PartialEq)]
74pub struct Context {
75 width: u16,
76 unbounded: bool,
77 tab_width: NonZeroU16,
78 box_chars: BoxChars,
79}
80
81impl Context {
82 /// A context `width` cells wide, with tab stops every [`TAB_WIDTH`]
83 /// cells and Unicode box characters.
84 #[must_use]
85 pub fn new(width: u16) -> Self {
86 Self {
87 width,
88 unbounded: false,
89 tab_width: TAB_WIDTH,
90 box_chars: BoxChars::default(),
91 }
92 }
93
94 /// A context with no width limit, so a widget takes its natural width.
95 ///
96 /// [`width`](Self::width) reports `u16::MAX`, so layout arithmetic needs
97 /// no special case, and [`is_unbounded`](Self::is_unbounded) tells a
98 /// caller that the width is no limit and not a real one. Every method that
99 /// returns a changed clone keeps the flag, including
100 /// [`at_width`](Self::at_width).
101 #[must_use]
102 pub fn unbounded() -> Self {
103 Self {
104 unbounded: true,
105 ..Self::new(u16::MAX)
106 }
107 }
108
109 /// This context at another width.
110 #[must_use]
111 pub fn at_width(&self, width: u16) -> Self {
112 Self {
113 width,
114 ..self.clone()
115 }
116 }
117
118 /// This context with tab stops every `tab_width` cells.
119 #[must_use]
120 pub fn at_tab_width(&self, tab_width: NonZeroU16) -> Self {
121 Self {
122 tab_width,
123 ..self.clone()
124 }
125 }
126
127 /// This context with `box_chars` for borders.
128 #[must_use]
129 pub fn using_box_chars(&self, box_chars: BoxChars) -> Self {
130 Self {
131 box_chars,
132 ..self.clone()
133 }
134 }
135
136 /// The width in cells that rendered lines should fit.
137 #[must_use]
138 pub fn width(&self) -> u16 {
139 self.width
140 }
141
142 /// Whether this context was made by [`unbounded`](Self::unbounded), so
143 /// [`width`](Self::width) is no limit.
144 #[must_use]
145 pub fn is_unbounded(&self) -> bool {
146 self.unbounded
147 }
148
149 /// The spacing of tab stops, in cells.
150 #[must_use]
151 pub fn tab_width(&self) -> NonZeroU16 {
152 self.tab_width
153 }
154
155 /// The characters to draw borders and rules with.
156 #[must_use]
157 pub fn box_chars(&self) -> BoxChars {
158 self.box_chars
159 }
160}
161
162/// Content that knows its own size and can draw itself at a width.
163///
164/// A container asks each child to [`measure`](Self::measure), decides the
165/// child's width, and renders the child at that width. The trait is dyn
166/// compatible, so a container can hold `Box<dyn Renderable>`. A reference to a
167/// widget and a `Box` of one are widgets too, so a container can borrow its
168/// children.
169///
170/// The `ctl_text` crate docs show a complete widget.
171pub trait Renderable {
172 /// The narrowest and widest this content can usefully be.
173 fn measure(&self, context: &Context) -> Measurement;
174
175 /// Lines no wider than `context.width()`, except a word or cluster that
176 /// the content's overflow policy keeps whole.
177 fn render(&self, context: &Context) -> Vec<Line>;
178}
179
180impl<T: Renderable + ?Sized> Renderable for &T {
181 fn measure(&self, context: &Context) -> Measurement {
182 (**self).measure(context)
183 }
184
185 fn render(&self, context: &Context) -> Vec<Line> {
186 (**self).render(context)
187 }
188}
189
190impl<T: Renderable + ?Sized> Renderable for Box<T> {
191 fn measure(&self, context: &Context) -> Measurement {
192 (**self).measure(context)
193 }
194
195 fn render(&self, context: &Context) -> Vec<Line> {
196 (**self).render(context)
197 }
198}