Skip to main content

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}