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