1use crate::{
2 buffer::Buffer,
3 layout::{Alignment, Rect},
4 style::Style,
5 symbols::line,
6 text::Spans,
7 widgets::{Borders, Widget},
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum BorderType {
12 Plain,
13 Rounded,
14 Double,
15 Thick,
16}
17
18impl BorderType {
19 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
31struct Padding {
32 pub left: u16,
33 pub right: u16,
34 pub top: u16,
35 pub bottom: u16,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct Block<'a> {
55 title: Option<Spans<'a>>,
57 title_alignment: Alignment,
60 borders: Borders,
62 border_style: Style,
64 border_type: BorderType,
67 style: Style,
69 padding: Padding,
71}
72
73impl<'a> Default for Block<'a> {
74 fn default() -> Block<'a> {
75 Block {
76 title: None,
77 title_alignment: Alignment::Left,
78 borders: Borders::NONE,
79 border_style: Default::default(),
80 border_type: BorderType::Plain,
81 style: Default::default(),
82 padding: Default::default(),
83 }
84 }
85}
86
87impl<'a> Block<'a> {
88 pub fn styled_borders(borders: Borders, border_style: impl Into<Style>) -> Block<'a> {
89 Block {
90 borders,
91 title: None,
92 title_alignment: Alignment::Left,
93 border_type: BorderType::Plain,
94 border_style: border_style.into(),
95 ..Default::default()
96 }
97 }
98
99 pub fn bordered(borders: Borders) -> Block<'a> {
100 Block {
101 borders,
102 title: None,
103 title_alignment: Alignment::Left,
104 border_type: BorderType::Plain,
105 ..Default::default()
106 }
107 }
108
109 pub fn styled(style: Style) -> Block<'a> {
110 Block {
111 style,
112 title: None,
113 title_alignment: Alignment::Left,
114 borders: Borders::NONE,
115 border_style: Default::default(),
116 border_type: BorderType::Plain,
117 padding: Default::default(),
118 }
119 }
120
121 pub fn title<T>(mut self, title: T) -> Block<'a>
122 where
123 T: Into<Spans<'a>>,
124 {
125 self.title = Some(title.into());
126 self
127 }
128
129 pub fn title_alignment(mut self, alignment: Alignment) -> Block<'a> {
130 self.title_alignment = alignment;
131 self
132 }
133
134 pub fn border_style(mut self, style: Style) -> Block<'a> {
135 self.border_style = style;
136 self
137 }
138
139 pub fn style(mut self, style: Style) -> Block<'a> {
140 self.style = style;
141 self
142 }
143
144 pub fn borders(mut self, flag: Borders) -> Block<'a> {
145 self.borders = flag;
146 self
147 }
148
149 pub fn border_type(mut self, border_type: BorderType) -> Block<'a> {
150 self.border_type = border_type;
151 self
152 }
153
154 pub fn padding(mut self, padding: u16) -> Block<'a> {
156 self.padding = Padding {
157 left: padding,
158 right: padding,
159 top: padding,
160 bottom: padding,
161 };
162 self
163 }
164
165 pub fn padding_all(mut self, left: u16, right: u16, top: u16, bottom: u16) -> Block<'a> {
167 self.padding = Padding {
168 left,
169 right,
170 top,
171 bottom,
172 };
173 self
174 }
175
176 pub fn padding_horizontal(mut self, padding: u16) -> Block<'a> {
178 self.padding.left = padding;
179 self.padding.right = padding;
180 self
181 }
182
183 pub fn padding_vertical(mut self, padding: u16) -> Block<'a> {
185 self.padding.top = padding;
186 self.padding.bottom = padding;
187 self
188 }
189
190 pub fn padding_left(mut self, padding: u16) -> Block<'a> {
192 self.padding.left = padding;
193 self
194 }
195
196 pub fn padding_right(mut self, padding: u16) -> Block<'a> {
198 self.padding.right = padding;
199 self
200 }
201
202 pub fn padding_top(mut self, padding: u16) -> Block<'a> {
204 self.padding.top = padding;
205 self
206 }
207
208 pub fn padding_bottom(mut self, padding: u16) -> Block<'a> {
210 self.padding.bottom = padding;
211 self
212 }
213
214 pub fn inner(&self, area: Rect) -> Rect {
216 let mut inner = area;
217
218 if self.borders.intersects(Borders::LEFT) {
219 inner.x = inner.x.saturating_add(1).min(inner.right());
220 inner.width = inner.width.saturating_sub(1);
221 }
222 if self.borders.intersects(Borders::TOP) || self.title.is_some() {
223 inner.y = inner.y.saturating_add(1).min(inner.bottom());
224 inner.height = inner.height.saturating_sub(1);
225 }
226 if self.borders.intersects(Borders::RIGHT) {
227 inner.width = inner.width.saturating_sub(1);
228 }
229 if self.borders.intersects(Borders::BOTTOM) {
230 inner.height = inner.height.saturating_sub(1);
231 }
232
233 inner.x = inner.x.saturating_add(self.padding.left).min(inner.right());
234 inner.y = inner.y.saturating_add(self.padding.top).min(inner.bottom());
235 inner.width = inner
236 .width
237 .saturating_sub(self.padding.left + self.padding.right);
238 inner.height = inner
239 .height
240 .saturating_sub(self.padding.top + self.padding.bottom);
241
242 inner
243 }
244}
245
246impl<'a> Widget for Block<'a> {
247 fn render(&mut self, area: Rect, buf: &mut Buffer) {
248 if area.area() == 0 {
249 return;
250 }
251 buf.set_style(area, self.style);
252 let symbols = BorderType::line_symbols(self.border_type);
253
254 if self.borders.intersects(Borders::LEFT) {
256 for y in area.top()..area.bottom() {
257 buf.get_mut(area.left(), y)
258 .set_symbol(symbols.vertical)
259 .set_style(self.border_style);
260 }
261 }
262 if self.borders.intersects(Borders::TOP) {
263 for x in area.left()..area.right() {
264 buf.get_mut(x, area.top())
265 .set_symbol(symbols.horizontal)
266 .set_style(self.border_style);
267 }
268 }
269 if self.borders.intersects(Borders::RIGHT) {
270 let x = area.right() - 1;
271 for y in area.top()..area.bottom() {
272 buf.get_mut(x, y)
273 .set_symbol(symbols.vertical)
274 .set_style(self.border_style);
275 }
276 }
277 if self.borders.intersects(Borders::BOTTOM) {
278 let y = area.bottom() - 1;
279 for x in area.left()..area.right() {
280 buf.get_mut(x, y)
281 .set_symbol(symbols.horizontal)
282 .set_style(self.border_style);
283 }
284 }
285
286 if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
288 buf.get_mut(area.right() - 1, area.bottom() - 1)
289 .set_symbol(symbols.bottom_right)
290 .set_style(self.border_style);
291 }
292 if self.borders.contains(Borders::RIGHT | Borders::TOP) {
293 buf.get_mut(area.right() - 1, area.top())
294 .set_symbol(symbols.top_right)
295 .set_style(self.border_style);
296 }
297 if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
298 buf.get_mut(area.left(), area.bottom() - 1)
299 .set_symbol(symbols.bottom_left)
300 .set_style(self.border_style);
301 }
302 if self.borders.contains(Borders::LEFT | Borders::TOP) {
303 buf.get_mut(area.left(), area.top())
304 .set_symbol(symbols.top_left)
305 .set_style(self.border_style);
306 }
307
308 if let Some(title) = self.title.as_mut() {
310 let left_border_dx = if self.borders.intersects(Borders::LEFT) {
311 1
312 } else {
313 0
314 };
315
316 let right_border_dx = if self.borders.intersects(Borders::RIGHT) {
317 1
318 } else {
319 0
320 };
321
322 let title_area_width = area
323 .width
324 .saturating_sub(left_border_dx)
325 .saturating_sub(right_border_dx);
326
327 let title_dx = match self.title_alignment {
328 Alignment::Left => left_border_dx,
329 Alignment::Center => area.width.saturating_sub(title.width() as u16) / 2,
330 Alignment::Right => area
331 .width
332 .saturating_sub(title.width() as u16)
333 .saturating_sub(right_border_dx),
334 };
335
336 let title_x = area.left() + title_dx;
337 let title_y = area.top();
338
339 buf.set_spans(title_x, title_y, &title, title_area_width);
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use crate::layout::Rect;
348
349 #[test]
350 fn inner_takes_into_account_the_borders() {
351 assert_eq!(
353 Block::default().inner(Rect::default()),
354 Rect {
355 x: 0,
356 y: 0,
357 width: 0,
358 height: 0
359 },
360 "no borders, width=0, height=0"
361 );
362 assert_eq!(
363 Block::default().inner(Rect {
364 x: 0,
365 y: 0,
366 width: 1,
367 height: 1
368 }),
369 Rect {
370 x: 0,
371 y: 0,
372 width: 1,
373 height: 1
374 },
375 "no borders, width=1, height=1"
376 );
377
378 assert_eq!(
380 Block::default().borders(Borders::LEFT).inner(Rect {
381 x: 0,
382 y: 0,
383 width: 0,
384 height: 1
385 }),
386 Rect {
387 x: 0,
388 y: 0,
389 width: 0,
390 height: 1
391 },
392 "left, width=0"
393 );
394 assert_eq!(
395 Block::default().borders(Borders::LEFT).inner(Rect {
396 x: 0,
397 y: 0,
398 width: 1,
399 height: 1
400 }),
401 Rect {
402 x: 1,
403 y: 0,
404 width: 0,
405 height: 1
406 },
407 "left, width=1"
408 );
409 assert_eq!(
410 Block::default().borders(Borders::LEFT).inner(Rect {
411 x: 0,
412 y: 0,
413 width: 2,
414 height: 1
415 }),
416 Rect {
417 x: 1,
418 y: 0,
419 width: 1,
420 height: 1
421 },
422 "left, width=2"
423 );
424
425 assert_eq!(
427 Block::default().borders(Borders::TOP).inner(Rect {
428 x: 0,
429 y: 0,
430 width: 1,
431 height: 0
432 }),
433 Rect {
434 x: 0,
435 y: 0,
436 width: 1,
437 height: 0
438 },
439 "top, height=0"
440 );
441 assert_eq!(
442 Block::default().borders(Borders::TOP).inner(Rect {
443 x: 0,
444 y: 0,
445 width: 1,
446 height: 1
447 }),
448 Rect {
449 x: 0,
450 y: 1,
451 width: 1,
452 height: 0
453 },
454 "top, height=1"
455 );
456 assert_eq!(
457 Block::default().borders(Borders::TOP).inner(Rect {
458 x: 0,
459 y: 0,
460 width: 1,
461 height: 2
462 }),
463 Rect {
464 x: 0,
465 y: 1,
466 width: 1,
467 height: 1
468 },
469 "top, height=2"
470 );
471
472 assert_eq!(
474 Block::default().borders(Borders::RIGHT).inner(Rect {
475 x: 0,
476 y: 0,
477 width: 0,
478 height: 1
479 }),
480 Rect {
481 x: 0,
482 y: 0,
483 width: 0,
484 height: 1
485 },
486 "right, width=0"
487 );
488 assert_eq!(
489 Block::default().borders(Borders::RIGHT).inner(Rect {
490 x: 0,
491 y: 0,
492 width: 1,
493 height: 1
494 }),
495 Rect {
496 x: 0,
497 y: 0,
498 width: 0,
499 height: 1
500 },
501 "right, width=1"
502 );
503 assert_eq!(
504 Block::default().borders(Borders::RIGHT).inner(Rect {
505 x: 0,
506 y: 0,
507 width: 2,
508 height: 1
509 }),
510 Rect {
511 x: 0,
512 y: 0,
513 width: 1,
514 height: 1
515 },
516 "right, width=2"
517 );
518
519 assert_eq!(
521 Block::default().borders(Borders::BOTTOM).inner(Rect {
522 x: 0,
523 y: 0,
524 width: 1,
525 height: 0
526 }),
527 Rect {
528 x: 0,
529 y: 0,
530 width: 1,
531 height: 0
532 },
533 "bottom, height=0"
534 );
535 assert_eq!(
536 Block::default().borders(Borders::BOTTOM).inner(Rect {
537 x: 0,
538 y: 0,
539 width: 1,
540 height: 1
541 }),
542 Rect {
543 x: 0,
544 y: 0,
545 width: 1,
546 height: 0
547 },
548 "bottom, height=1"
549 );
550 assert_eq!(
551 Block::default().borders(Borders::BOTTOM).inner(Rect {
552 x: 0,
553 y: 0,
554 width: 1,
555 height: 2
556 }),
557 Rect {
558 x: 0,
559 y: 0,
560 width: 1,
561 height: 1
562 },
563 "bottom, height=2"
564 );
565
566 assert_eq!(
568 Block::default()
569 .borders(Borders::ALL)
570 .inner(Rect::default()),
571 Rect {
572 x: 0,
573 y: 0,
574 width: 0,
575 height: 0
576 },
577 "all borders, width=0, height=0"
578 );
579 assert_eq!(
580 Block::default().borders(Borders::ALL).inner(Rect {
581 x: 0,
582 y: 0,
583 width: 1,
584 height: 1
585 }),
586 Rect {
587 x: 1,
588 y: 1,
589 width: 0,
590 height: 0,
591 },
592 "all borders, width=1, height=1"
593 );
594 assert_eq!(
595 Block::default().borders(Borders::ALL).inner(Rect {
596 x: 0,
597 y: 0,
598 width: 2,
599 height: 2,
600 }),
601 Rect {
602 x: 1,
603 y: 1,
604 width: 0,
605 height: 0,
606 },
607 "all borders, width=2, height=2"
608 );
609 assert_eq!(
610 Block::default().borders(Borders::ALL).inner(Rect {
611 x: 0,
612 y: 0,
613 width: 3,
614 height: 3,
615 }),
616 Rect {
617 x: 1,
618 y: 1,
619 width: 1,
620 height: 1,
621 },
622 "all borders, width=3, height=3"
623 );
624 }
625
626 #[test]
627 fn inner_takes_into_account_the_title() {
628 assert_eq!(
629 Block::default().title("Test").inner(Rect {
630 x: 0,
631 y: 0,
632 width: 0,
633 height: 1,
634 }),
635 Rect {
636 x: 0,
637 y: 1,
638 width: 0,
639 height: 0,
640 },
641 );
642 assert_eq!(
643 Block::default()
644 .title("Test")
645 .title_alignment(Alignment::Center)
646 .inner(Rect {
647 x: 0,
648 y: 0,
649 width: 0,
650 height: 1,
651 }),
652 Rect {
653 x: 0,
654 y: 1,
655 width: 0,
656 height: 0,
657 },
658 );
659 assert_eq!(
660 Block::default()
661 .title("Test")
662 .title_alignment(Alignment::Right)
663 .inner(Rect {
664 x: 0,
665 y: 0,
666 width: 0,
667 height: 1,
668 }),
669 Rect {
670 x: 0,
671 y: 1,
672 width: 0,
673 height: 0,
674 },
675 );
676 }
677}