ratatui/widgets/
borders.rs

1use std::fmt;
2
3use bitflags::bitflags;
4
5bitflags! {
6    /// Bitflags that can be composed to set the visible borders essentially on the block widget.
7    #[derive(Default, Clone, Copy, Eq, PartialEq, Hash)]
8    pub struct Borders: u8 {
9        /// Show no border (default)
10        const NONE   = 0b0000;
11        /// Show the top border
12        const TOP    = 0b0001;
13        /// Show the right border
14        const RIGHT  = 0b0010;
15        /// Show the bottom border
16        const BOTTOM = 0b0100;
17        /// Show the left border
18        const LEFT   = 0b1000;
19        /// Show all borders
20        const ALL = Self::TOP.bits() | Self::RIGHT.bits() | Self::BOTTOM.bits() | Self::LEFT.bits();
21    }
22}
23
24/// Implement the `Debug` trait for the `Borders` bitflags. This is a manual implementation to
25/// display the flags in a more readable way. The default implementation would display the
26/// flags as 'Border(0x0)' for `Borders::NONE` for example.
27impl fmt::Debug for Borders {
28    /// Display the Borders bitflags as a list of names. For example, `Borders::NONE` will be
29    /// displayed as `NONE` and `Borders::ALL` will be displayed as `ALL`. If multiple flags are
30    /// set, they will be displayed separated by a pipe character.
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        if self.is_empty() {
33            return write!(f, "NONE");
34        }
35        if self.is_all() {
36            return write!(f, "ALL");
37        }
38        let mut first = true;
39        for (name, border) in self.iter_names() {
40            if border == Self::NONE {
41                continue;
42            }
43            if first {
44                write!(f, "{name}")?;
45                first = false;
46            } else {
47                write!(f, " | {name}")?;
48            }
49        }
50        Ok(())
51    }
52}
53
54/// Macro that constructs and returns a combination of the [`Borders`] object from TOP, BOTTOM, LEFT
55/// and RIGHT.
56///
57/// When used with NONE you should consider omitting this completely. For ALL you should consider
58/// [`Block::bordered()`](crate::widgets::Block::bordered) instead.
59///
60/// ## Examples
61///
62/// ```
63/// use ratatui::{
64///     border,
65///     widgets::{Block, Borders},
66/// };
67///
68/// Block::new()
69///     .title("Construct Borders and use them in place")
70///     .borders(border!(TOP, BOTTOM));
71/// ```
72///
73/// `border!` can be called with any number of individual sides:
74///
75/// ```
76/// use ratatui::{border, widgets::Borders};
77/// let right_open = border!(TOP, LEFT, BOTTOM);
78/// assert_eq!(right_open, Borders::TOP | Borders::LEFT | Borders::BOTTOM);
79/// ```
80///
81/// Single borders work but using `Borders::` directly would be simpler.
82///
83/// ```
84/// use ratatui::{border, widgets::Borders};
85///
86/// assert_eq!(border!(TOP), Borders::TOP);
87/// assert_eq!(border!(ALL), Borders::ALL);
88/// assert_eq!(border!(), Borders::NONE);
89/// ```
90#[cfg(feature = "macros")]
91#[macro_export]
92macro_rules! border {
93    () => {
94        Borders::NONE
95    };
96    ($b:ident) => {
97        Borders::$b
98    };
99    ($first:ident,$($other:ident),*) => {
100        Borders::$first
101        $(
102            .union(Borders::$other)
103        )*
104    };
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_borders_debug() {
113        assert_eq!(format!("{:?}", Borders::empty()), "NONE");
114        assert_eq!(format!("{:?}", Borders::NONE), "NONE");
115        assert_eq!(format!("{:?}", Borders::TOP), "TOP");
116        assert_eq!(format!("{:?}", Borders::BOTTOM), "BOTTOM");
117        assert_eq!(format!("{:?}", Borders::LEFT), "LEFT");
118        assert_eq!(format!("{:?}", Borders::RIGHT), "RIGHT");
119        assert_eq!(format!("{:?}", Borders::ALL), "ALL");
120        assert_eq!(format!("{:?}", Borders::all()), "ALL");
121
122        assert_eq!(
123            format!("{:?}", Borders::TOP | Borders::BOTTOM),
124            "TOP | BOTTOM"
125        );
126    }
127}
128
129#[cfg(all(test, feature = "macros"))]
130mod macro_tests {
131    use super::*;
132
133    #[test]
134    fn can_be_const() {
135        const NOTHING: Borders = border!();
136        const JUST_TOP: Borders = border!(TOP);
137        const TOP_BOTTOM: Borders = border!(TOP, BOTTOM);
138        const RIGHT_OPEN: Borders = border!(TOP, LEFT, BOTTOM);
139
140        assert_eq!(NOTHING, Borders::NONE);
141        assert_eq!(JUST_TOP, Borders::TOP);
142        assert_eq!(TOP_BOTTOM, Borders::TOP | Borders::BOTTOM);
143        assert_eq!(RIGHT_OPEN, Borders::TOP | Borders::LEFT | Borders::BOTTOM);
144    }
145
146    #[test]
147    fn border_empty() {
148        let empty = Borders::NONE;
149        assert_eq!(empty, border!());
150    }
151
152    #[test]
153    fn border_all() {
154        let all = Borders::ALL;
155        assert_eq!(all, border!(ALL));
156        assert_eq!(all, border!(TOP, BOTTOM, LEFT, RIGHT));
157    }
158
159    #[test]
160    fn border_left_right() {
161        let left_right = Borders::from_bits(Borders::LEFT.bits() | Borders::RIGHT.bits());
162        assert_eq!(left_right, Some(border!(RIGHT, LEFT)));
163    }
164}