tui_box_text/
lib.rs

1//! A [Ratatui] widget to draw delightfully boxy text with line-drawing characters. Part of the
2//! [tui-widgets] suite by [Joshka].
3//!
4//! ![Demo](https://vhs.charm.sh/vhs-6ldj2r9v3mIaSzk8H7Jp8t.gif)
5//!
6//! [![Crate badge]][Crate]
7//! [![Docs Badge]][Docs]
8//! [![Deps Badge]][Dependency Status]
9//! [![License Badge]][License]
10//! [![Coverage Badge]][Coverage]
11//! [![Discord Badge]][Ratatui Discord]
12//!
13//! [GitHub Repository] · [API Docs] · [Examples] · [Changelog] · [Contributing]
14//!
15//! # Usage
16//!
17//! Create a `BoxChar` and render it into a region of your frame.
18//!
19//! ```rust
20//! use tui_box_text::BoxChar;
21//!
22//! # fn draw(frame: &mut ratatui::Frame) {
23//! let letter = BoxChar::new('A');
24//! frame.render_widget(&letter, frame.area());
25//! # }
26//! ```
27//!
28//! # More widgets
29//!
30//! For the full suite of widgets, see [tui-widgets].
31//!
32//! [Crate]: https://crates.io/crates/tui-box-text
33//! [Docs]: https://docs.rs/tui-box-text/
34//! [Dependency Status]: https://deps.rs/repo/github/joshka/tui-widgets
35//! [Coverage]: https://app.codecov.io/gh/joshka/tui-widgets
36//! [Ratatui Discord]: https://discord.gg/pMCEU9hNEj
37//! [Crate badge]: https://img.shields.io/crates/v/tui-box-text?logo=rust&style=flat
38//! [Docs Badge]: https://img.shields.io/docsrs/tui-box-text?logo=rust&style=flat
39//! [Deps Badge]: https://deps.rs/repo/github/joshka/tui-widgets/status.svg?style=flat
40//! [License Badge]: https://img.shields.io/crates/l/tui-box-text?style=flat
41//! [License]: https://github.com/joshka/tui-widgets/blob/main/LICENSE-MIT
42//! [Coverage Badge]:
43//!     https://img.shields.io/codecov/c/github/joshka/tui-widgets?logo=codecov&style=flat
44//! [Discord Badge]: https://img.shields.io/discord/1070692720437383208?logo=discord&style=flat
45//!
46//! [GitHub Repository]: https://github.com/joshka/tui-widgets
47//! [API Docs]: https://docs.rs/tui-box-text/
48//! [Examples]: https://github.com/joshka/tui-widgets/tree/main/tui-box-text/examples
49//! [Changelog]: https://github.com/joshka/tui-widgets/blob/main/tui-box-text/CHANGELOG.md
50//! [Contributing]: https://github.com/joshka/tui-widgets/blob/main/CONTRIBUTING.md
51//! [Joshka]: https://github.com/joshka
52//! [tui-widgets]: https://crates.io/crates/tui-widgets
53
54use std::collections::HashMap;
55use std::iter::zip;
56use std::sync::LazyLock;
57
58use ratatui_core::buffer::Buffer;
59use ratatui_core::layout::Rect;
60use ratatui_core::widgets::Widget;
61
62pub struct BoxChar(char);
63
64impl BoxChar {
65    pub const fn new(c: char) -> Self {
66        Self(c)
67    }
68}
69
70impl Widget for &BoxChar {
71    fn render(self, area: Rect, buf: &mut Buffer) {
72        let c = self
73            .0
74            .to_uppercase() // TODO: add support for lower case characters
75            .next()
76            .and_then(|c| CHARS.get(&c))
77            .unwrap_or(&" ");
78        let lines = c.lines().collect::<Vec<_>>();
79        for (line, row) in zip(lines, area.rows()) {
80            for (char, cell) in zip(line.chars(), row.columns()) {
81                buf[cell.as_position()].set_symbol(&char.to_string());
82            }
83        }
84    }
85}
86
87/// A macro for creating a hash table that maps single characters to strings.
88macro_rules! char_table {
89    ( $($char:expr => $repr:expr),* $(,)? ) => {
90        {
91            let mut table = ::std::collections::HashMap::new();
92            $(
93                table.insert($char, ::indoc::indoc! {$repr});
94            )*
95            table
96        }
97    };
98}
99
100/// A hash table that maps single characters to strings that are 3 lines high and made up of box
101/// drawing characters.
102static CHARS: LazyLock<HashMap<char, &str>> = LazyLock::new(|| {
103    char_table!(
104        ' ' => " ",
105        '!' => "│
106107                ╵",
108        '"' => "╭╭",
109        '#' => "┼─┼
110                ┼─┼",
111        '$' => "╭┼╴
112                └┼┐
113                ╶┼╯",
114        '%' => "o╱
115                ╱o",
116        '&' => "╭─╮
117                ╭╲╯
118                ╰─╲",
119        '\'' => "╭",
120        '(' => "╭
121122                ╰",
123        ')' => "╮
124125                ╯",
126        '*' => "
127        
128                *
129                ",
130        '+' => "
131132                ╶┼╴
133                 ╵",
134        ',' => "
135
136                
137                ╯",
138        '-' => "
139
140                ──
141                 ",
142        '.' => "
143
144                .
145                 ",
146        '/' => "
147148149                ",
150        '0' => "╭─╮
151                │╱│
152                ╰─╯",
153        '1' => "
154                 ╶┐
155156                 ─┴─",
157        '2' => "╶─╮
158                ┌─┘
159                └─╴",
160        '3' => "╶─╮
161                ╶─┤
162                ╶─╯",
163        '4' => "╷ ╷
164                ╰─┤
165                  ╵",
166        '5' => "┌─╴
167                └─╮
168                ╰─╯",
169        '6' => "╭─╴
170                ├─╮
171                ╰─╯",
172        '7' => "╶─┐
173174                ╵  ",
175        '8' => "╭─╮
176                ├─┤
177                ╰─╯",
178        '9' => "╭─╮
179                ╰─┤
180                ╶─╯",
181        ':' => "╷
182183184                 ",
185        ';' => "╷
186187                ╯",
188        '<' => "
189190191                 ",
192        '=' => "
193                ──
194                ──",
195        '>' => "
196197198                 ",
199        '?' => "
200                ╶─╮
201                 ╭╯
202                 ╷",
203        '@' => "╭─╮
204                ╭╮│
205                ╰┴╯",
206        'A' => "╭─╮
207                ├─┤
208                ╵ ╵",
209        'B' => "┌╮
210                ├┴╮
211                ╰─╯",
212        'C' => "╭─╮
213214                ╰─╯",
215        'D' => "┌─╮
216                │ │
217                └─╯",
218        'E' => "┌─╴
219                ├─
220                └─╴",
221        'F' => "┌─╴
222                ├─
223                ╵  ",
224        'G' => "╭─╮
225                │─╮
226                ╰─╯",
227        'H' => "╷ ╷
228                ├─┤
229                ╵ ╵",
230        'I' => "╶┬╴
231232                ╶┴╴",
233        'J' => " ╶┐
234235                ╰─╯",
236        'K' => "╷╭
237                ├┴╮
238                ╵ ╵",
239        'L' => "╷
240241                └──",
242        'M' => "╭┬╮
243                │││
244                ╵╵╵",
245        'N' => "╭─╮
246                │ │
247                ╵ ╵",
248        'O' => "╭─╮
249                │ │
250                ╰─╯",
251        'P' => "┌─╮
252                ├─╯
253                ╵  ",
254        'Q' => "╭─╮
255                │ │
256                ╰─╳",
257        'R' => "┌─╮
258                ├┬╯
259                ╵╰ ",
260        'S' => "╭─╮
261                ╰─╮ 
262                ╰─╯",
263        'T' => "
264                ╶┬╴
265266                 ╵",
267        'U' => "╷ ╷
268                │ │
269                ╰─╯",
270        'V' => "╷ ╷
271                │ │
272                └─╯",
273        'W' => "╷╷╷
274                │││
275                ╰┴╯",
276        'X' => "╮ ╭
277                ╰─╮ 
278                ╯ ╰",
279        'Y' => "╮ ╭
280                ╰┬╯ 
281                 ╵",
282        'Z' => "╶─╮
283284                ╰─╴",
285        '[' => "┌─
286287                └─",
288        '\\' => "
289290291                ",
292        ']' => "─┐
293294                ─┘",
295        '^' => "╱╲",
296        '_' => "
297
298                ──",
299        '`' => "╮",
300        '{' => "
301302303                ╰",
304        '|' => "│
305306                │",
307        '}' => "╮
308309                ╯",
310        '~' => "
311                ╭╮
312                 ╰╯",
313    )
314});