ratatui_widgets/
borders.rs

1//! Border related types ([`Borders`], [`BorderType`]) and a macro to create borders ([`border`]).
2use alloc::fmt;
3
4use bitflags::bitflags;
5use ratatui_core::symbols::border;
6use strum::{Display, EnumString};
7
8bitflags! {
9    /// Bitflags that can be composed to set the visible borders essentially on the block widget.
10    #[derive(Default, Clone, Copy, Eq, PartialEq, Hash)]
11    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12    pub struct Borders: u8 {
13        /// Show the top border
14        const TOP    = 0b0001;
15        /// Show the right border
16        const RIGHT  = 0b0010;
17        /// Show the bottom border
18        const BOTTOM = 0b0100;
19        /// Show the left border
20        const LEFT   = 0b1000;
21        /// Show all borders
22        const ALL = Self::TOP.bits() | Self::RIGHT.bits() | Self::BOTTOM.bits() | Self::LEFT.bits();
23    }
24}
25
26impl Borders {
27    /// Show no border (default)
28    pub const NONE: Self = Self::empty();
29}
30
31/// The type of border of a [`Block`](crate::block::Block).
32///
33/// See the [`borders`](crate::block::Block::borders) method of `Block` to configure its borders.
34#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub enum BorderType {
37    /// A plain, simple border.
38    ///
39    /// This is the default
40    ///
41    /// # Example
42    ///
43    /// ```plain
44    /// ┌───────┐
45    /// │       │
46    /// └───────┘
47    /// ```
48    #[default]
49    Plain,
50    /// A plain border with rounded corners.
51    ///
52    /// # Example
53    ///
54    /// ```plain
55    /// ╭───────╮
56    /// │       │
57    /// ╰───────╯
58    /// ```
59    Rounded,
60    /// A doubled border.
61    ///
62    /// Note this uses one character that draws two lines.
63    ///
64    /// # Example
65    ///
66    /// ```plain
67    /// ╔═══════╗
68    /// ║       ║
69    /// ╚═══════╝
70    /// ```
71    Double,
72    /// A thick border.
73    ///
74    /// # Example
75    ///
76    /// ```plain
77    /// ┏━━━━━━━┓
78    /// ┃       ┃
79    /// ┗━━━━━━━┛
80    /// ```
81    Thick,
82    /// A light double-dashed border.
83    ///
84    /// ```plain
85    /// ┌╌╌╌╌╌╌╌┐
86    /// ╎       ╎
87    /// └╌╌╌╌╌╌╌┘
88    /// ```
89    LightDoubleDashed,
90    /// A heavy double-dashed border.
91    ///
92    /// ```plain
93    /// ┏╍╍╍╍╍╍╍┓
94    /// ╏       ╏
95    /// ┗╍╍╍╍╍╍╍┛
96    /// ```
97    HeavyDoubleDashed,
98    /// A light triple-dashed border.
99    ///
100    /// ```plain
101    /// ┌┄┄┄┄┄┄┄┐
102    /// ┆       ┆
103    /// └┄┄┄┄┄┄┄┘
104    /// ```
105    LightTripleDashed,
106    /// A heavy triple-dashed border.
107    ///
108    /// ```plain
109    /// ┏┅┅┅┅┅┅┅┓
110    /// ┇       ┇
111    /// ┗┅┅┅┅┅┅┅┛
112    /// ```
113    HeavyTripleDashed,
114    /// A light quadruple-dashed border.
115    ///
116    /// ```plain
117    /// ┌┈┈┈┈┈┈┈┐
118    /// ┊       ┊
119    /// └┈┈┈┈┈┈┈┘
120    /// ```
121    LightQuadrupleDashed,
122    /// A heavy quadruple-dashed border.
123    ///
124    /// ```plain
125    /// ┏┉┉┉┉┉┉┉┓
126    /// ┋       ┋
127    /// ┗┉┉┉┉┉┉┉┛
128    /// ```
129    HeavyQuadrupleDashed,
130    /// A border with a single line on the inside of a half block.
131    ///
132    /// # Example
133    ///
134    /// ```plain
135    /// ▗▄▄▄▄▄▄▄▖
136    /// ▐       ▌
137    /// ▐       ▌
138    /// ▝▀▀▀▀▀▀▀▘
139    QuadrantInside,
140
141    /// A border with a single line on the outside of a half block.
142    ///
143    /// # Example
144    ///
145    /// ```plain
146    /// ▛▀▀▀▀▀▀▀▜
147    /// ▌       ▐
148    /// ▌       ▐
149    /// ▙▄▄▄▄▄▄▄▟
150    QuadrantOutside,
151}
152
153impl BorderType {
154    /// Convert this `BorderType` into the corresponding [`Set`](border::Set) of border symbols.
155    pub const fn border_symbols<'a>(border_type: Self) -> border::Set<'a> {
156        match border_type {
157            Self::Plain => border::PLAIN,
158            Self::Rounded => border::ROUNDED,
159            Self::Double => border::DOUBLE,
160            Self::Thick => border::THICK,
161            Self::LightDoubleDashed => border::LIGHT_DOUBLE_DASHED,
162            Self::HeavyDoubleDashed => border::HEAVY_DOUBLE_DASHED,
163            Self::LightTripleDashed => border::LIGHT_TRIPLE_DASHED,
164            Self::HeavyTripleDashed => border::HEAVY_TRIPLE_DASHED,
165            Self::LightQuadrupleDashed => border::LIGHT_QUADRUPLE_DASHED,
166            Self::HeavyQuadrupleDashed => border::HEAVY_QUADRUPLE_DASHED,
167            Self::QuadrantInside => border::QUADRANT_INSIDE,
168            Self::QuadrantOutside => border::QUADRANT_OUTSIDE,
169        }
170    }
171
172    /// Convert this `BorderType` into the corresponding [`Set`](border::Set) of border symbols.
173    pub const fn to_border_set<'a>(self) -> border::Set<'a> {
174        Self::border_symbols(self)
175    }
176}
177
178impl fmt::Debug for Borders {
179    /// Display the Borders bitflags as a list of names.
180    ///
181    /// `Borders::NONE` is displayed as `NONE` and `Borders::ALL` is displayed as `ALL`. If multiple
182    /// flags are set, they are otherwise displayed separated by a pipe character.
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        if self.is_empty() {
185            return write!(f, "NONE");
186        }
187        if self.is_all() {
188            return write!(f, "ALL");
189        }
190        let mut names = self.iter_names().map(|(name, _)| name);
191        if let Some(first) = names.next() {
192            write!(f, "{first}")?;
193        }
194        for name in names {
195            write!(f, " | {name}")?;
196        }
197        Ok(())
198    }
199}
200
201/// Macro that constructs and returns a combination of the [`Borders`] object from TOP, BOTTOM, LEFT
202/// and RIGHT.
203///
204/// When used with NONE you should consider omitting this completely. For ALL you should consider
205/// [`Block::bordered()`](crate::block::Block::bordered) instead.
206///
207/// ## Examples
208///
209/// ```
210/// use ratatui::border;
211/// use ratatui::widgets::Block;
212///
213/// Block::new()
214///     .title("Construct Borders and use them in place")
215///     .borders(border!(TOP, BOTTOM));
216/// ```
217///
218/// `border!` can be called with any number of individual sides:
219///
220/// ```
221/// use ratatui::border;
222/// use ratatui::widgets::Borders;
223/// let right_open = border!(TOP, LEFT, BOTTOM);
224/// assert_eq!(right_open, Borders::TOP | Borders::LEFT | Borders::BOTTOM);
225/// ```
226///
227/// Single borders work but using `Borders::` directly would be simpler.
228///
229/// ```
230/// use ratatui::border;
231/// use ratatui::widgets::Borders;
232///
233/// assert_eq!(border!(TOP), Borders::TOP);
234/// assert_eq!(border!(ALL), Borders::ALL);
235/// assert_eq!(border!(), Borders::NONE);
236/// ```
237#[macro_export]
238macro_rules! border {
239    () => {
240        $crate::borders::Borders::NONE
241    };
242    ($b:ident) => {
243        $crate::borders::Borders::$b
244    };
245    ($first:ident,$($other:ident),*) => {
246        $crate::borders::Borders::$first
247        $(
248            .union($crate::borders::Borders::$other)
249        )*
250    };
251}
252
253#[cfg(test)]
254mod tests {
255    use alloc::format;
256
257    use super::*;
258
259    #[test]
260    fn test_borders_debug() {
261        assert_eq!(format!("{:?}", Borders::empty()), "NONE");
262        assert_eq!(format!("{:?}", Borders::NONE), "NONE");
263        assert_eq!(format!("{:?}", Borders::TOP), "TOP");
264        assert_eq!(format!("{:?}", Borders::BOTTOM), "BOTTOM");
265        assert_eq!(format!("{:?}", Borders::LEFT), "LEFT");
266        assert_eq!(format!("{:?}", Borders::RIGHT), "RIGHT");
267        assert_eq!(format!("{:?}", Borders::ALL), "ALL");
268        assert_eq!(format!("{:?}", Borders::all()), "ALL");
269
270        assert_eq!(
271            format!("{:?}", Borders::TOP | Borders::BOTTOM),
272            "TOP | BOTTOM"
273        );
274    }
275
276    #[test]
277    fn can_be_const() {
278        const NOTHING: Borders = border!();
279        const JUST_TOP: Borders = border!(TOP);
280        const TOP_BOTTOM: Borders = border!(TOP, BOTTOM);
281        const RIGHT_OPEN: Borders = border!(TOP, LEFT, BOTTOM);
282
283        assert_eq!(NOTHING, Borders::NONE);
284        assert_eq!(JUST_TOP, Borders::TOP);
285        assert_eq!(TOP_BOTTOM, Borders::TOP | Borders::BOTTOM);
286        assert_eq!(RIGHT_OPEN, Borders::TOP | Borders::LEFT | Borders::BOTTOM);
287    }
288
289    #[test]
290    fn border_empty() {
291        let empty = Borders::NONE;
292        assert_eq!(empty, border!());
293    }
294
295    #[test]
296    fn border_all() {
297        let all = Borders::ALL;
298        assert_eq!(all, border!(ALL));
299        assert_eq!(all, border!(TOP, BOTTOM, LEFT, RIGHT));
300    }
301
302    #[test]
303    fn border_left_right() {
304        let left_right = Borders::from_bits(Borders::LEFT.bits() | Borders::RIGHT.bits());
305        assert_eq!(left_right, Some(border!(RIGHT, LEFT)));
306    }
307}