1 use crate::{
2     buffer::Buffer,
3     layout::{Alignment, Rect},
4     style::Style,
5     symbols::line,
6     text::{Span, Spans},
7     widgets::{Borders, Widget},
8 };
9 
10 #[derive(Debug, Clone, Copy, PartialEq)]
11 pub enum BorderType {
12     Plain,
13     Rounded,
14     Double,
15     Thick,
16 }
17 
18 impl BorderType {
line_symbols(border_type: BorderType) -> line::Set19     pub fn line_symbols(border_type: BorderType) -> line::Set {
20         match border_type {
21             BorderType::Plain => line::NORMAL,
22             BorderType::Rounded => line::ROUNDED,
23             BorderType::Double => line::DOUBLE,
24             BorderType::Thick => line::THICK,
25         }
26     }
27 }
28 
29 /// Base widget to be used with all upper level ones. It may be used to display a box border around
30 /// the widget and/or add a title.
31 ///
32 /// # Examples
33 ///
34 /// ```
35 /// # use tui::widgets::{Block, BorderType, Borders};
36 /// # use tui::style::{Style, Color};
37 /// Block::default()
38 ///     .title("Block")
39 ///     .borders(Borders::LEFT | Borders::RIGHT)
40 ///     .border_style(Style::default().fg(Color::White))
41 ///     .border_type(BorderType::Rounded)
42 ///     .style(Style::default().bg(Color::Black));
43 /// ```
44 #[derive(Debug, Clone, PartialEq)]
45 pub struct Block<'a> {
46     /// Optional title place on the upper left of the block
47     title: Option<Spans<'a>>,
48     /// Title alignment. The default is top left of the block, but one can choose to place
49     /// title in the top middle, or top right of the block
50     title_alignment: Alignment,
51     /// Visible borders
52     borders: Borders,
53     /// Border style
54     border_style: Style,
55     /// Type of the border. The default is plain lines but one can choose to have rounded corners
56     /// or doubled lines instead.
57     border_type: BorderType,
58     /// Widget style
59     style: Style,
60 }
61 
62 impl<'a> Default for Block<'a> {
default() -> Block<'a>63     fn default() -> Block<'a> {
64         Block {
65             title: None,
66             title_alignment: Alignment::Left,
67             borders: Borders::NONE,
68             border_style: Default::default(),
69             border_type: BorderType::Plain,
70             style: Default::default(),
71         }
72     }
73 }
74 
75 impl<'a> Block<'a> {
title<T>(mut self, title: T) -> Block<'a> where T: Into<Spans<'a>>,76     pub fn title<T>(mut self, title: T) -> Block<'a>
77     where
78         T: Into<Spans<'a>>,
79     {
80         self.title = Some(title.into());
81         self
82     }
83 
84     #[deprecated(
85         since = "0.10.0",
86         note = "You should use styling capabilities of `text::Spans` given as argument of the `title` method to apply styling to the title."
87     )]
title_style(mut self, style: Style) -> Block<'a>88     pub fn title_style(mut self, style: Style) -> Block<'a> {
89         if let Some(t) = self.title {
90             let title = String::from(t);
91             self.title = Some(Spans::from(Span::styled(title, style)));
92         }
93         self
94     }
95 
title_alignment(mut self, alignment: Alignment) -> Block<'a>96     pub fn title_alignment(mut self, alignment: Alignment) -> Block<'a> {
97         self.title_alignment = alignment;
98         self
99     }
100 
border_style(mut self, style: Style) -> Block<'a>101     pub fn border_style(mut self, style: Style) -> Block<'a> {
102         self.border_style = style;
103         self
104     }
105 
style(mut self, style: Style) -> Block<'a>106     pub fn style(mut self, style: Style) -> Block<'a> {
107         self.style = style;
108         self
109     }
110 
borders(mut self, flag: Borders) -> Block<'a>111     pub fn borders(mut self, flag: Borders) -> Block<'a> {
112         self.borders = flag;
113         self
114     }
115 
border_type(mut self, border_type: BorderType) -> Block<'a>116     pub fn border_type(mut self, border_type: BorderType) -> Block<'a> {
117         self.border_type = border_type;
118         self
119     }
120 
121     /// Compute the inner area of a block based on its border visibility rules.
inner(&self, area: Rect) -> Rect122     pub fn inner(&self, area: Rect) -> Rect {
123         let mut inner = area;
124         if self.borders.intersects(Borders::LEFT) {
125             inner.x = inner.x.saturating_add(1).min(inner.right());
126             inner.width = inner.width.saturating_sub(1);
127         }
128         if self.borders.intersects(Borders::TOP) || self.title.is_some() {
129             inner.y = inner.y.saturating_add(1).min(inner.bottom());
130             inner.height = inner.height.saturating_sub(1);
131         }
132         if self.borders.intersects(Borders::RIGHT) {
133             inner.width = inner.width.saturating_sub(1);
134         }
135         if self.borders.intersects(Borders::BOTTOM) {
136             inner.height = inner.height.saturating_sub(1);
137         }
138         inner
139     }
140 }
141 
142 impl<'a> Widget for Block<'a> {
render(self, area: Rect, buf: &mut Buffer)143     fn render(self, area: Rect, buf: &mut Buffer) {
144         if area.area() == 0 {
145             return;
146         }
147         buf.set_style(area, self.style);
148         let symbols = BorderType::line_symbols(self.border_type);
149 
150         // Sides
151         if self.borders.intersects(Borders::LEFT) {
152             for y in area.top()..area.bottom() {
153                 buf.get_mut(area.left(), y)
154                     .set_symbol(symbols.vertical)
155                     .set_style(self.border_style);
156             }
157         }
158         if self.borders.intersects(Borders::TOP) {
159             for x in area.left()..area.right() {
160                 buf.get_mut(x, area.top())
161                     .set_symbol(symbols.horizontal)
162                     .set_style(self.border_style);
163             }
164         }
165         if self.borders.intersects(Borders::RIGHT) {
166             let x = area.right() - 1;
167             for y in area.top()..area.bottom() {
168                 buf.get_mut(x, y)
169                     .set_symbol(symbols.vertical)
170                     .set_style(self.border_style);
171             }
172         }
173         if self.borders.intersects(Borders::BOTTOM) {
174             let y = area.bottom() - 1;
175             for x in area.left()..area.right() {
176                 buf.get_mut(x, y)
177                     .set_symbol(symbols.horizontal)
178                     .set_style(self.border_style);
179             }
180         }
181 
182         // Corners
183         if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
184             buf.get_mut(area.right() - 1, area.bottom() - 1)
185                 .set_symbol(symbols.bottom_right)
186                 .set_style(self.border_style);
187         }
188         if self.borders.contains(Borders::RIGHT | Borders::TOP) {
189             buf.get_mut(area.right() - 1, area.top())
190                 .set_symbol(symbols.top_right)
191                 .set_style(self.border_style);
192         }
193         if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
194             buf.get_mut(area.left(), area.bottom() - 1)
195                 .set_symbol(symbols.bottom_left)
196                 .set_style(self.border_style);
197         }
198         if self.borders.contains(Borders::LEFT | Borders::TOP) {
199             buf.get_mut(area.left(), area.top())
200                 .set_symbol(symbols.top_left)
201                 .set_style(self.border_style);
202         }
203 
204         // Title
205         if let Some(title) = self.title {
206             let left_border_dx = if self.borders.intersects(Borders::LEFT) {
207                 1
208             } else {
209                 0
210             };
211 
212             let right_border_dx = if self.borders.intersects(Borders::RIGHT) {
213                 1
214             } else {
215                 0
216             };
217 
218             let title_area_width = area
219                 .width
220                 .saturating_sub(left_border_dx)
221                 .saturating_sub(right_border_dx);
222 
223             let title_dx = match self.title_alignment {
224                 Alignment::Left => left_border_dx,
225                 Alignment::Center => area.width.saturating_sub(title.width() as u16) / 2,
226                 Alignment::Right => area
227                     .width
228                     .saturating_sub(title.width() as u16)
229                     .saturating_sub(right_border_dx),
230             };
231 
232             let title_x = area.left() + title_dx;
233             let title_y = area.top();
234 
235             buf.set_spans(title_x, title_y, &title, title_area_width);
236         }
237     }
238 }
239 
240 #[cfg(test)]
241 mod tests {
242     use super::*;
243     use crate::layout::Rect;
244 
245     #[test]
inner_takes_into_account_the_borders()246     fn inner_takes_into_account_the_borders() {
247         // No borders
248         assert_eq!(
249             Block::default().inner(Rect::default()),
250             Rect {
251                 x: 0,
252                 y: 0,
253                 width: 0,
254                 height: 0
255             },
256             "no borders, width=0, height=0"
257         );
258         assert_eq!(
259             Block::default().inner(Rect {
260                 x: 0,
261                 y: 0,
262                 width: 1,
263                 height: 1
264             }),
265             Rect {
266                 x: 0,
267                 y: 0,
268                 width: 1,
269                 height: 1
270             },
271             "no borders, width=1, height=1"
272         );
273 
274         // Left border
275         assert_eq!(
276             Block::default().borders(Borders::LEFT).inner(Rect {
277                 x: 0,
278                 y: 0,
279                 width: 0,
280                 height: 1
281             }),
282             Rect {
283                 x: 0,
284                 y: 0,
285                 width: 0,
286                 height: 1
287             },
288             "left, width=0"
289         );
290         assert_eq!(
291             Block::default().borders(Borders::LEFT).inner(Rect {
292                 x: 0,
293                 y: 0,
294                 width: 1,
295                 height: 1
296             }),
297             Rect {
298                 x: 1,
299                 y: 0,
300                 width: 0,
301                 height: 1
302             },
303             "left, width=1"
304         );
305         assert_eq!(
306             Block::default().borders(Borders::LEFT).inner(Rect {
307                 x: 0,
308                 y: 0,
309                 width: 2,
310                 height: 1
311             }),
312             Rect {
313                 x: 1,
314                 y: 0,
315                 width: 1,
316                 height: 1
317             },
318             "left, width=2"
319         );
320 
321         // Top border
322         assert_eq!(
323             Block::default().borders(Borders::TOP).inner(Rect {
324                 x: 0,
325                 y: 0,
326                 width: 1,
327                 height: 0
328             }),
329             Rect {
330                 x: 0,
331                 y: 0,
332                 width: 1,
333                 height: 0
334             },
335             "top, height=0"
336         );
337         assert_eq!(
338             Block::default().borders(Borders::TOP).inner(Rect {
339                 x: 0,
340                 y: 0,
341                 width: 1,
342                 height: 1
343             }),
344             Rect {
345                 x: 0,
346                 y: 1,
347                 width: 1,
348                 height: 0
349             },
350             "top, height=1"
351         );
352         assert_eq!(
353             Block::default().borders(Borders::TOP).inner(Rect {
354                 x: 0,
355                 y: 0,
356                 width: 1,
357                 height: 2
358             }),
359             Rect {
360                 x: 0,
361                 y: 1,
362                 width: 1,
363                 height: 1
364             },
365             "top, height=2"
366         );
367 
368         // Right border
369         assert_eq!(
370             Block::default().borders(Borders::RIGHT).inner(Rect {
371                 x: 0,
372                 y: 0,
373                 width: 0,
374                 height: 1
375             }),
376             Rect {
377                 x: 0,
378                 y: 0,
379                 width: 0,
380                 height: 1
381             },
382             "right, width=0"
383         );
384         assert_eq!(
385             Block::default().borders(Borders::RIGHT).inner(Rect {
386                 x: 0,
387                 y: 0,
388                 width: 1,
389                 height: 1
390             }),
391             Rect {
392                 x: 0,
393                 y: 0,
394                 width: 0,
395                 height: 1
396             },
397             "right, width=1"
398         );
399         assert_eq!(
400             Block::default().borders(Borders::RIGHT).inner(Rect {
401                 x: 0,
402                 y: 0,
403                 width: 2,
404                 height: 1
405             }),
406             Rect {
407                 x: 0,
408                 y: 0,
409                 width: 1,
410                 height: 1
411             },
412             "right, width=2"
413         );
414 
415         // Bottom border
416         assert_eq!(
417             Block::default().borders(Borders::BOTTOM).inner(Rect {
418                 x: 0,
419                 y: 0,
420                 width: 1,
421                 height: 0
422             }),
423             Rect {
424                 x: 0,
425                 y: 0,
426                 width: 1,
427                 height: 0
428             },
429             "bottom, height=0"
430         );
431         assert_eq!(
432             Block::default().borders(Borders::BOTTOM).inner(Rect {
433                 x: 0,
434                 y: 0,
435                 width: 1,
436                 height: 1
437             }),
438             Rect {
439                 x: 0,
440                 y: 0,
441                 width: 1,
442                 height: 0
443             },
444             "bottom, height=1"
445         );
446         assert_eq!(
447             Block::default().borders(Borders::BOTTOM).inner(Rect {
448                 x: 0,
449                 y: 0,
450                 width: 1,
451                 height: 2
452             }),
453             Rect {
454                 x: 0,
455                 y: 0,
456                 width: 1,
457                 height: 1
458             },
459             "bottom, height=2"
460         );
461 
462         // All borders
463         assert_eq!(
464             Block::default()
465                 .borders(Borders::ALL)
466                 .inner(Rect::default()),
467             Rect {
468                 x: 0,
469                 y: 0,
470                 width: 0,
471                 height: 0
472             },
473             "all borders, width=0, height=0"
474         );
475         assert_eq!(
476             Block::default().borders(Borders::ALL).inner(Rect {
477                 x: 0,
478                 y: 0,
479                 width: 1,
480                 height: 1
481             }),
482             Rect {
483                 x: 1,
484                 y: 1,
485                 width: 0,
486                 height: 0,
487             },
488             "all borders, width=1, height=1"
489         );
490         assert_eq!(
491             Block::default().borders(Borders::ALL).inner(Rect {
492                 x: 0,
493                 y: 0,
494                 width: 2,
495                 height: 2,
496             }),
497             Rect {
498                 x: 1,
499                 y: 1,
500                 width: 0,
501                 height: 0,
502             },
503             "all borders, width=2, height=2"
504         );
505         assert_eq!(
506             Block::default().borders(Borders::ALL).inner(Rect {
507                 x: 0,
508                 y: 0,
509                 width: 3,
510                 height: 3,
511             }),
512             Rect {
513                 x: 1,
514                 y: 1,
515                 width: 1,
516                 height: 1,
517             },
518             "all borders, width=3, height=3"
519         );
520     }
521 
522     #[test]
inner_takes_into_account_the_title()523     fn inner_takes_into_account_the_title() {
524         assert_eq!(
525             Block::default().title("Test").inner(Rect {
526                 x: 0,
527                 y: 0,
528                 width: 0,
529                 height: 1,
530             }),
531             Rect {
532                 x: 0,
533                 y: 1,
534                 width: 0,
535                 height: 0,
536             },
537         );
538         assert_eq!(
539             Block::default()
540                 .title("Test")
541                 .title_alignment(Alignment::Center)
542                 .inner(Rect {
543                     x: 0,
544                     y: 0,
545                     width: 0,
546                     height: 1,
547                 }),
548             Rect {
549                 x: 0,
550                 y: 1,
551                 width: 0,
552                 height: 0,
553             },
554         );
555         assert_eq!(
556             Block::default()
557                 .title("Test")
558                 .title_alignment(Alignment::Right)
559                 .inner(Rect {
560                     x: 0,
561                     y: 0,
562                     width: 0,
563                     height: 1,
564                 }),
565             Rect {
566                 x: 0,
567                 y: 1,
568                 width: 0,
569                 height: 0,
570             },
571         );
572     }
573 }
574