Skip to main content

rumatui_tui/widgets/
block.rs

1use crate::buffer::Buffer;
2use crate::layout::Rect;
3use crate::style::Style;
4use crate::symbols::line;
5use crate::widgets::{Borders, Widget};
6
7#[derive(Debug, Copy, Clone)]
8pub enum BorderType {
9    Plain,
10    Rounded,
11    Double,
12    Thick,
13}
14
15impl BorderType {
16    pub fn line_symbols(border_type: BorderType) -> line::Set {
17        match border_type {
18            BorderType::Plain => line::NORMAL,
19            BorderType::Rounded => line::ROUNDED,
20            BorderType::Double => line::DOUBLE,
21            BorderType::Thick => line::THICK,
22        }
23    }
24}
25
26/// Base widget to be used with all upper level ones. It may be used to display a box border around
27/// the widget and/or add a title.
28///
29/// # Examples
30///
31/// ```
32/// # use rumatui_tui::widgets::{Block, BorderType, Borders};
33/// # use rumatui_tui::style::{Style, Color};
34/// Block::default()
35///     .title("Block")
36///     .title_style(Style::default().fg(Color::Red))
37///     .borders(Borders::LEFT | Borders::RIGHT)
38///     .border_style(Style::default().fg(Color::White))
39///     .border_type(BorderType::Rounded)
40///     .style(Style::default().bg(Color::Black));
41/// ```
42#[derive(Clone, Copy)]
43pub struct Block<'a> {
44    /// Optional title place on the upper left of the block
45    title: Option<&'a str>,
46    /// Title style
47    title_style: Style,
48    /// Visible borders
49    borders: Borders,
50    /// Border style
51    border_style: Style,
52    /// Type of the border. The default is plain lines but one can choose to have rounded corners
53    /// or doubled lines instead.
54    border_type: BorderType,
55    /// Widget style
56    style: Style,
57}
58
59impl<'a> Default for Block<'a> {
60    fn default() -> Block<'a> {
61        Block {
62            title: None,
63            title_style: Default::default(),
64            borders: Borders::NONE,
65            border_style: Default::default(),
66            border_type: BorderType::Plain,
67            style: Default::default(),
68        }
69    }
70}
71
72impl<'a> Block<'a> {
73    pub fn title(mut self, title: &'a str) -> Block<'a> {
74        self.title = Some(title);
75        self
76    }
77
78    pub fn title_style(mut self, style: Style) -> Block<'a> {
79        self.title_style = style;
80        self
81    }
82
83    pub fn border_style(mut self, style: Style) -> Block<'a> {
84        self.border_style = style;
85        self
86    }
87
88    pub fn style(mut self, style: Style) -> Block<'a> {
89        self.style = style;
90        self
91    }
92
93    pub fn borders(mut self, flag: Borders) -> Block<'a> {
94        self.borders = flag;
95        self
96    }
97
98    pub fn border_type(mut self, border_type: BorderType) -> Block<'a> {
99        self.border_type = border_type;
100        self
101    }
102
103    /// Compute the inner area of a block based on its border visibility rules.
104    pub fn inner(&self, area: Rect) -> Rect {
105        if area.width < 2 || area.height < 2 {
106            return Rect::default();
107        }
108        let mut inner = area;
109        if self.borders.intersects(Borders::LEFT) {
110            inner.x += 1;
111            inner.width -= 1;
112        }
113        if self.borders.intersects(Borders::TOP) || self.title.is_some() {
114            inner.y += 1;
115            inner.height -= 1;
116        }
117        if self.borders.intersects(Borders::RIGHT) {
118            inner.width -= 1;
119        }
120        if self.borders.intersects(Borders::BOTTOM) {
121            inner.height -= 1;
122        }
123        inner
124    }
125}
126
127impl<'a> Widget for Block<'a> {
128    fn render(self, area: Rect, buf: &mut Buffer) {
129        if area.width < 2 || area.height < 2 {
130            return;
131        }
132
133        buf.set_background(area, self.style.bg);
134
135        let symbols = BorderType::line_symbols(self.border_type);
136        // Sides
137        if self.borders.intersects(Borders::LEFT) {
138            for y in area.top()..area.bottom() {
139                buf.get_mut(area.left(), y)
140                    .set_symbol(symbols.vertical)
141                    .set_style(self.border_style);
142            }
143        }
144        if self.borders.intersects(Borders::TOP) {
145            for x in area.left()..area.right() {
146                buf.get_mut(x, area.top())
147                    .set_symbol(symbols.horizontal)
148                    .set_style(self.border_style);
149            }
150        }
151        if self.borders.intersects(Borders::RIGHT) {
152            let x = area.right() - 1;
153            for y in area.top()..area.bottom() {
154                buf.get_mut(x, y)
155                    .set_symbol(symbols.vertical)
156                    .set_style(self.border_style);
157            }
158        }
159        if self.borders.intersects(Borders::BOTTOM) {
160            let y = area.bottom() - 1;
161            for x in area.left()..area.right() {
162                buf.get_mut(x, y)
163                    .set_symbol(symbols.horizontal)
164                    .set_style(self.border_style);
165            }
166        }
167
168        // Corners
169        if self.borders.contains(Borders::LEFT | Borders::TOP) {
170            buf.get_mut(area.left(), area.top())
171                .set_symbol(symbols.top_left)
172                .set_style(self.border_style);
173        }
174        if self.borders.contains(Borders::RIGHT | Borders::TOP) {
175            buf.get_mut(area.right() - 1, area.top())
176                .set_symbol(symbols.top_right)
177                .set_style(self.border_style);
178        }
179        if self.borders.contains(Borders::LEFT | Borders::BOTTOM) {
180            buf.get_mut(area.left(), area.bottom() - 1)
181                .set_symbol(symbols.bottom_left)
182                .set_style(self.border_style);
183        }
184        if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) {
185            buf.get_mut(area.right() - 1, area.bottom() - 1)
186                .set_symbol(symbols.bottom_right)
187                .set_style(self.border_style);
188        }
189
190        if let Some(title) = self.title {
191            let lx = if self.borders.intersects(Borders::LEFT) {
192                1
193            } else {
194                0
195            };
196            let rx = if self.borders.intersects(Borders::RIGHT) {
197                1
198            } else {
199                0
200            };
201            let width = area.width - lx - rx;
202            buf.set_stringn(
203                area.left() + lx,
204                area.top(),
205                title,
206                width as usize,
207                self.title_style,
208            );
209        }
210    }
211}