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