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}