ratatui/symbols/
border.rs

1use crate::symbols::{block, line};
2
3#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
4pub struct Set {
5    pub top_left: &'static str,
6    pub top_right: &'static str,
7    pub bottom_left: &'static str,
8    pub bottom_right: &'static str,
9    pub vertical_left: &'static str,
10    pub vertical_right: &'static str,
11    pub horizontal_top: &'static str,
12    pub horizontal_bottom: &'static str,
13}
14
15impl Default for Set {
16    fn default() -> Self {
17        PLAIN
18    }
19}
20
21/// Border Set with a single line width
22///
23/// ```text
24/// ┌─────┐
25/// │xxxxx│
26/// │xxxxx│
27/// └─────┘
28/// ```
29pub const PLAIN: Set = Set {
30    top_left: line::NORMAL.top_left,
31    top_right: line::NORMAL.top_right,
32    bottom_left: line::NORMAL.bottom_left,
33    bottom_right: line::NORMAL.bottom_right,
34    vertical_left: line::NORMAL.vertical,
35    vertical_right: line::NORMAL.vertical,
36    horizontal_top: line::NORMAL.horizontal,
37    horizontal_bottom: line::NORMAL.horizontal,
38};
39
40/// Border Set with a single line width and rounded corners
41///
42/// ```text
43/// ╭─────╮
44/// │xxxxx│
45/// │xxxxx│
46/// ╰─────╯
47/// ```
48pub const ROUNDED: Set = Set {
49    top_left: line::ROUNDED.top_left,
50    top_right: line::ROUNDED.top_right,
51    bottom_left: line::ROUNDED.bottom_left,
52    bottom_right: line::ROUNDED.bottom_right,
53    vertical_left: line::ROUNDED.vertical,
54    vertical_right: line::ROUNDED.vertical,
55    horizontal_top: line::ROUNDED.horizontal,
56    horizontal_bottom: line::ROUNDED.horizontal,
57};
58
59/// Border Set with a double line width
60///
61/// ```text
62/// ╔═════╗
63/// ║xxxxx║
64/// ║xxxxx║
65/// ╚═════╝
66/// ```
67pub const DOUBLE: Set = Set {
68    top_left: line::DOUBLE.top_left,
69    top_right: line::DOUBLE.top_right,
70    bottom_left: line::DOUBLE.bottom_left,
71    bottom_right: line::DOUBLE.bottom_right,
72    vertical_left: line::DOUBLE.vertical,
73    vertical_right: line::DOUBLE.vertical,
74    horizontal_top: line::DOUBLE.horizontal,
75    horizontal_bottom: line::DOUBLE.horizontal,
76};
77
78/// Border Set with a thick line width
79///
80/// ```text
81/// ┏━━━━━┓
82/// ┃xxxxx┃
83/// ┃xxxxx┃
84/// ┗━━━━━┛
85/// ```
86pub const THICK: Set = Set {
87    top_left: line::THICK.top_left,
88    top_right: line::THICK.top_right,
89    bottom_left: line::THICK.bottom_left,
90    bottom_right: line::THICK.bottom_right,
91    vertical_left: line::THICK.vertical,
92    vertical_right: line::THICK.vertical,
93    horizontal_top: line::THICK.horizontal,
94    horizontal_bottom: line::THICK.horizontal,
95};
96
97pub const QUADRANT_TOP_LEFT: &str = "▘";
98pub const QUADRANT_TOP_RIGHT: &str = "▝";
99pub const QUADRANT_BOTTOM_LEFT: &str = "▖";
100pub const QUADRANT_BOTTOM_RIGHT: &str = "▗";
101pub const QUADRANT_TOP_HALF: &str = "▀";
102pub const QUADRANT_BOTTOM_HALF: &str = "▄";
103pub const QUADRANT_LEFT_HALF: &str = "▌";
104pub const QUADRANT_RIGHT_HALF: &str = "▐";
105pub const QUADRANT_TOP_LEFT_BOTTOM_LEFT_BOTTOM_RIGHT: &str = "▙";
106pub const QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT: &str = "▛";
107pub const QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT: &str = "▜";
108pub const QUADRANT_TOP_RIGHT_BOTTOM_LEFT_BOTTOM_RIGHT: &str = "▟";
109pub const QUADRANT_TOP_LEFT_BOTTOM_RIGHT: &str = "▚";
110pub const QUADRANT_TOP_RIGHT_BOTTOM_LEFT: &str = "▞";
111pub const QUADRANT_BLOCK: &str = "█";
112
113/// Quadrant used for setting a border outside a block by one half cell "pixel".
114///
115/// ```text
116/// ▛▀▀▀▀▀▜
117/// ▌xxxxx▐
118/// ▌xxxxx▐
119/// ▙▄▄▄▄▄▟
120/// ```
121pub const QUADRANT_OUTSIDE: Set = Set {
122    top_left: QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT,
123    top_right: QUADRANT_TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT,
124    bottom_left: QUADRANT_TOP_LEFT_BOTTOM_LEFT_BOTTOM_RIGHT,
125    bottom_right: QUADRANT_TOP_RIGHT_BOTTOM_LEFT_BOTTOM_RIGHT,
126    vertical_left: QUADRANT_LEFT_HALF,
127    vertical_right: QUADRANT_RIGHT_HALF,
128    horizontal_top: QUADRANT_TOP_HALF,
129    horizontal_bottom: QUADRANT_BOTTOM_HALF,
130};
131
132/// Quadrant used for setting a border inside a block by one half cell "pixel".
133///
134/// ```text
135/// ▗▄▄▄▄▄▖
136/// ▐xxxxx▌
137/// ▐xxxxx▌
138/// ▝▀▀▀▀▀▘
139/// ```
140pub const QUADRANT_INSIDE: Set = Set {
141    top_right: QUADRANT_BOTTOM_LEFT,
142    top_left: QUADRANT_BOTTOM_RIGHT,
143    bottom_right: QUADRANT_TOP_LEFT,
144    bottom_left: QUADRANT_TOP_RIGHT,
145    vertical_left: QUADRANT_RIGHT_HALF,
146    vertical_right: QUADRANT_LEFT_HALF,
147    horizontal_top: QUADRANT_BOTTOM_HALF,
148    horizontal_bottom: QUADRANT_TOP_HALF,
149};
150
151pub const ONE_EIGHTH_TOP_EIGHT: &str = "▔";
152pub const ONE_EIGHTH_BOTTOM_EIGHT: &str = "▁";
153pub const ONE_EIGHTH_LEFT_EIGHT: &str = "▏";
154pub const ONE_EIGHTH_RIGHT_EIGHT: &str = "▕";
155
156/// Wide border set based on McGugan box technique
157///
158/// ```text
159/// ▁▁▁▁▁▁▁
160/// ▏xxxxx▕
161/// ▏xxxxx▕
162/// ▔▔▔▔▔▔▔
163/// ```
164#[allow(clippy::doc_markdown)]
165pub const ONE_EIGHTH_WIDE: Set = Set {
166    top_right: ONE_EIGHTH_BOTTOM_EIGHT,
167    top_left: ONE_EIGHTH_BOTTOM_EIGHT,
168    bottom_right: ONE_EIGHTH_TOP_EIGHT,
169    bottom_left: ONE_EIGHTH_TOP_EIGHT,
170    vertical_left: ONE_EIGHTH_LEFT_EIGHT,
171    vertical_right: ONE_EIGHTH_RIGHT_EIGHT,
172    horizontal_top: ONE_EIGHTH_BOTTOM_EIGHT,
173    horizontal_bottom: ONE_EIGHTH_TOP_EIGHT,
174};
175
176/// Tall border set based on McGugan box technique
177///
178/// ```text
179/// ▕▔▔▏
180/// ▕xx▏
181/// ▕xx▏
182/// ▕▁▁▏
183/// ```
184#[allow(clippy::doc_markdown)]
185pub const ONE_EIGHTH_TALL: Set = Set {
186    top_right: ONE_EIGHTH_LEFT_EIGHT,
187    top_left: ONE_EIGHTH_RIGHT_EIGHT,
188    bottom_right: ONE_EIGHTH_LEFT_EIGHT,
189    bottom_left: ONE_EIGHTH_RIGHT_EIGHT,
190    vertical_left: ONE_EIGHTH_RIGHT_EIGHT,
191    vertical_right: ONE_EIGHTH_LEFT_EIGHT,
192    horizontal_top: ONE_EIGHTH_TOP_EIGHT,
193    horizontal_bottom: ONE_EIGHTH_BOTTOM_EIGHT,
194};
195
196/// Wide proportional (visually equal width and height) border with using set of quadrants.
197///
198/// The border is created by using half blocks for top and bottom, and full
199/// blocks for right and left sides to make horizontal and vertical borders seem equal.
200///
201/// ```text
202/// ▄▄▄▄
203/// █xx█
204/// █xx█
205/// ▀▀▀▀
206/// ```
207pub const PROPORTIONAL_WIDE: Set = Set {
208    top_right: QUADRANT_BOTTOM_HALF,
209    top_left: QUADRANT_BOTTOM_HALF,
210    bottom_right: QUADRANT_TOP_HALF,
211    bottom_left: QUADRANT_TOP_HALF,
212    vertical_left: QUADRANT_BLOCK,
213    vertical_right: QUADRANT_BLOCK,
214    horizontal_top: QUADRANT_BOTTOM_HALF,
215    horizontal_bottom: QUADRANT_TOP_HALF,
216};
217
218/// Tall proportional (visually equal width and height) border with using set of quadrants.
219///
220/// The border is created by using full blocks for all sides, except for the top and bottom,
221/// which use half blocks to make horizontal and vertical borders seem equal.
222///
223/// ```text
224/// ▕█▀▀█
225/// ▕█xx█
226/// ▕█xx█
227/// ▕█▄▄█
228/// ```
229pub const PROPORTIONAL_TALL: Set = Set {
230    top_right: QUADRANT_BLOCK,
231    top_left: QUADRANT_BLOCK,
232    bottom_right: QUADRANT_BLOCK,
233    bottom_left: QUADRANT_BLOCK,
234    vertical_left: QUADRANT_BLOCK,
235    vertical_right: QUADRANT_BLOCK,
236    horizontal_top: QUADRANT_TOP_HALF,
237    horizontal_bottom: QUADRANT_BOTTOM_HALF,
238};
239
240/// Solid border set
241///
242/// The border is created by using full blocks for all sides.
243///
244/// ```text
245/// ████
246/// █xx█
247/// █xx█
248/// ████
249/// ```
250pub const FULL: Set = Set {
251    top_left: block::FULL,
252    top_right: block::FULL,
253    bottom_left: block::FULL,
254    bottom_right: block::FULL,
255    vertical_left: block::FULL,
256    vertical_right: block::FULL,
257    horizontal_top: block::FULL,
258    horizontal_bottom: block::FULL,
259};
260
261/// Empty border set
262///
263/// The border is created by using empty strings for all sides.
264///
265/// This is useful for ensuring that the border style is applied to a border on a block with a title
266/// without actually drawing a border.
267///
268/// ░ Example
269///
270/// `░` represents the content in the area not covered by the border to make it easier to see the
271/// blank symbols.
272///
273/// ```text
274/// ░░░░░░░░
275/// ░░    ░░
276/// ░░ ░░ ░░
277/// ░░ ░░ ░░
278/// ░░    ░░
279/// ░░░░░░░░
280/// ```
281pub const EMPTY: Set = Set {
282    top_left: " ",
283    top_right: " ",
284    bottom_left: " ",
285    bottom_right: " ",
286    vertical_left: " ",
287    vertical_right: " ",
288    horizontal_top: " ",
289    horizontal_bottom: " ",
290};
291
292#[cfg(test)]
293mod tests {
294    use indoc::{formatdoc, indoc};
295
296    use super::*;
297
298    #[test]
299    fn default() {
300        assert_eq!(Set::default(), PLAIN);
301    }
302
303    /// A helper function to render a border set to a string.
304    ///
305    /// '░' (U+2591 Light Shade) is used as a placeholder for empty space to make it easier to see
306    /// the size of the border symbols.
307    fn render(set: Set) -> String {
308        formatdoc!(
309            "░░░░░░
310             ░{}{}{}{}░
311             ░{}░░{}░
312             ░{}░░{}░
313             ░{}{}{}{}░
314             ░░░░░░",
315            set.top_left,
316            set.horizontal_top,
317            set.horizontal_top,
318            set.top_right,
319            set.vertical_left,
320            set.vertical_right,
321            set.vertical_left,
322            set.vertical_right,
323            set.bottom_left,
324            set.horizontal_bottom,
325            set.horizontal_bottom,
326            set.bottom_right
327        )
328    }
329
330    #[test]
331    fn plain() {
332        assert_eq!(
333            render(PLAIN),
334            indoc!(
335                "░░░░░░
336                 ░┌──┐░
337                 ░│░░│░
338                 ░│░░│░
339                 ░└──┘░
340                 ░░░░░░"
341            )
342        );
343    }
344
345    #[test]
346    fn rounded() {
347        assert_eq!(
348            render(ROUNDED),
349            indoc!(
350                "░░░░░░
351                 ░╭──╮░
352                 ░│░░│░
353                 ░│░░│░
354                 ░╰──╯░
355                 ░░░░░░"
356            )
357        );
358    }
359
360    #[test]
361    fn double() {
362        assert_eq!(
363            render(DOUBLE),
364            indoc!(
365                "░░░░░░
366                 ░╔══╗░
367                 ░║░░║░
368                 ░║░░║░
369                 ░╚══╝░
370                 ░░░░░░"
371            )
372        );
373    }
374
375    #[test]
376    fn thick() {
377        assert_eq!(
378            render(THICK),
379            indoc!(
380                "░░░░░░
381                 ░┏━━┓░
382                 ░┃░░┃░
383                 ░┃░░┃░
384                 ░┗━━┛░
385                 ░░░░░░"
386            )
387        );
388    }
389
390    #[test]
391    fn quadrant_outside() {
392        assert_eq!(
393            render(QUADRANT_OUTSIDE),
394            indoc!(
395                "░░░░░░
396                 ░▛▀▀▜░
397                 ░▌░░▐░
398                 ░▌░░▐░
399                 ░▙▄▄▟░
400                 ░░░░░░"
401            )
402        );
403    }
404
405    #[test]
406    fn quadrant_inside() {
407        assert_eq!(
408            render(QUADRANT_INSIDE),
409            indoc!(
410                "░░░░░░
411                 ░▗▄▄▖░
412                 ░▐░░▌░
413                 ░▐░░▌░
414                 ░▝▀▀▘░
415                 ░░░░░░"
416            )
417        );
418    }
419
420    #[test]
421    fn one_eighth_wide() {
422        assert_eq!(
423            render(ONE_EIGHTH_WIDE),
424            indoc!(
425                "░░░░░░
426                 ░▁▁▁▁░
427                 ░▏░░▕░
428                 ░▏░░▕░
429                 ░▔▔▔▔░
430                 ░░░░░░"
431            )
432        );
433    }
434
435    #[test]
436    fn one_eighth_tall() {
437        assert_eq!(
438            render(ONE_EIGHTH_TALL),
439            indoc!(
440                "░░░░░░
441                 ░▕▔▔▏░
442                 ░▕░░▏░
443                 ░▕░░▏░
444                 ░▕▁▁▏░
445                 ░░░░░░"
446            )
447        );
448    }
449
450    #[test]
451    fn proportional_wide() {
452        assert_eq!(
453            render(PROPORTIONAL_WIDE),
454            indoc!(
455                "░░░░░░
456                 ░▄▄▄▄░
457                 ░█░░█░
458                 ░█░░█░
459                 ░▀▀▀▀░
460                 ░░░░░░"
461            )
462        );
463    }
464
465    #[test]
466    fn proportional_tall() {
467        assert_eq!(
468            render(PROPORTIONAL_TALL),
469            indoc!(
470                "░░░░░░
471                 ░█▀▀█░
472                 ░█░░█░
473                 ░█░░█░
474                 ░█▄▄█░
475                 ░░░░░░"
476            )
477        );
478    }
479
480    #[test]
481    fn full() {
482        assert_eq!(
483            render(FULL),
484            indoc!(
485                "░░░░░░
486                 ░████░
487                 ░█░░█░
488                 ░█░░█░
489                 ░████░
490                 ░░░░░░"
491            )
492        );
493    }
494
495    #[test]
496    fn empty() {
497        assert_eq!(
498            render(EMPTY),
499            indoc!(
500                "░░░░░░
501                 ░    ░
502                 ░ ░░ ░
503                 ░ ░░ ░
504                 ░    ░
505                 ░░░░░░"
506            )
507        );
508    }
509}