Skip to content
This repository has been archived by the owner on Aug 6, 2023. It is now read-only.

Added feature to align title of the block #462

Merged
merged 5 commits into from May 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 23 additions & 8 deletions examples/block.rs
Expand Up @@ -6,7 +6,7 @@ use std::{error::Error, io};
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
use tui::{
backend::TermionBackend,
layout::{Constraint, Direction, Layout},
layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Modifier, Style},
text::Span,
widgets::{Block, BorderType, Borders},
Expand All @@ -30,21 +30,28 @@ fn main() -> Result<(), Box<dyn Error>> {
// Just draw the block and the group on the same area and build the group
// with at least a margin of 1
let size = f.size();

// Surounding block
let block = Block::default()
.borders(Borders::ALL)
.title("Main block with round corners")
.title_alignment(Alignment::Center)
.border_type(BorderType::Rounded);
f.render_widget(block, size);

let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(4)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(f.size());

// Top two inner blocks
let top_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[0]);

// Top left inner block with green background
let block = Block::default()
.title(vec![
Span::styled("With", Style::default().fg(Color::Yellow)),
Expand All @@ -53,21 +60,29 @@ fn main() -> Result<(), Box<dyn Error>> {
.style(Style::default().bg(Color::Green));
f.render_widget(block, top_chunks[0]);

let block = Block::default().title(Span::styled(
"Styled title",
Style::default()
.fg(Color::White)
.bg(Color::Red)
.add_modifier(Modifier::BOLD),
));
// Top right inner block with styled title aligned to the right
let block = Block::default()
.title(Span::styled(
"Styled title",
Style::default()
.fg(Color::White)
.bg(Color::Red)
.add_modifier(Modifier::BOLD),
))
.title_alignment(Alignment::Right);
f.render_widget(block, top_chunks[1]);

// Bottom two inner blocks
let bottom_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.split(chunks[1]);

// Bottom left block with all default borders
let block = Block::default().title("With borders").borders(Borders::ALL);
f.render_widget(block, bottom_chunks[0]);

// Bottom right block with styled left and right border
let block = Block::default()
.title("With styled borders and doubled borders")
.border_style(Style::default().fg(Color::Cyan))
Expand Down
72 changes: 67 additions & 5 deletions src/widgets/block.rs
@@ -1,6 +1,6 @@
use crate::{
buffer::Buffer,
layout::Rect,
layout::{Alignment, Rect},
style::Style,
symbols::line,
text::{Span, Spans},
Expand Down Expand Up @@ -45,6 +45,9 @@ impl BorderType {
pub struct Block<'a> {
/// Optional title place on the upper left of the block
title: Option<Spans<'a>>,
/// Title alignment. The default is top left of the block, but one can choose to place
/// title in the top middle, or top right of the block
title_alignment: Alignment,
/// Visible borders
borders: Borders,
/// Border style
Expand All @@ -60,6 +63,7 @@ impl<'a> Default for Block<'a> {
fn default() -> Block<'a> {
Block {
title: None,
title_alignment: Alignment::Left,
borders: Borders::NONE,
border_style: Default::default(),
border_type: BorderType::Plain,
Expand Down Expand Up @@ -89,6 +93,11 @@ impl<'a> Block<'a> {
self
}

pub fn title_alignment(mut self, alignment: Alignment) -> Block<'a> {
self.title_alignment = alignment;
self
}

pub fn border_style(mut self, style: Style) -> Block<'a> {
self.border_style = style;
self
Expand Down Expand Up @@ -192,19 +201,38 @@ impl<'a> Widget for Block<'a> {
.set_style(self.border_style);
}

// Title
if let Some(title) = self.title {
let lx = if self.borders.intersects(Borders::LEFT) {
let left_border_dx = if self.borders.intersects(Borders::LEFT) {
1
} else {
0
};
let rx = if self.borders.intersects(Borders::RIGHT) {

let right_border_dx = if self.borders.intersects(Borders::RIGHT) {
1
} else {
0
};
let width = area.width.saturating_sub(lx).saturating_sub(rx);
buf.set_spans(area.left() + lx, area.top(), &title, width);

let title_area_width = area
.width
.saturating_sub(left_border_dx)
.saturating_sub(right_border_dx);

let title_dx = match self.title_alignment {
Alignment::Left => left_border_dx,
Alignment::Center => area.width.saturating_sub(title.width() as u16) / 2,
Alignment::Right => area
.width
.saturating_sub(title.width() as u16)
.saturating_sub(right_border_dx),
};

let title_x = area.left() + title_dx;
let title_y = area.top();

buf.set_spans(title_x, title_y, &title, title_area_width);
}
}
}
Expand Down Expand Up @@ -507,5 +535,39 @@ mod tests {
height: 0,
},
);
assert_eq!(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title_alignment is not taken into account in Block::inner. Therefore, is there a reason to add those test cases here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added them to show that title_alignment does not effect Block::inner. I am not sure, if that is necessary.

Block::default()
.title("Test")
.title_alignment(Alignment::Center)
.inner(Rect {
x: 0,
y: 0,
width: 0,
height: 1,
}),
Rect {
x: 0,
y: 1,
width: 0,
height: 0,
},
);
assert_eq!(
Block::default()
.title("Test")
.title_alignment(Alignment::Right)
.inner(Rect {
x: 0,
y: 0,
width: 0,
height: 1,
}),
Rect {
x: 0,
y: 1,
width: 0,
height: 0,
},
);
}
}
135 changes: 134 additions & 1 deletion tests/widgets_block.rs
@@ -1,7 +1,7 @@
use tui::{
backend::TestBackend,
buffer::Buffer,
layout::Rect,
layout::{Alignment, Rect},
style::{Color, Style},
text::Span,
widgets::{Block, Borders},
Expand Down Expand Up @@ -211,3 +211,136 @@ fn widgets_block_renders_on_small_areas() {
Buffer::with_lines(vec!["┌Test─"]),
);
}

#[test]
fn widgets_block_title_alignment() {
let test_case = |alignment, borders, expected| {
let backend = TestBackend::new(15, 2);
let mut terminal = Terminal::new(backend).unwrap();

let block = Block::default()
.title(Span::styled("Title", Style::default()))
.title_alignment(alignment)
.borders(borders);

let area = Rect {
x: 1,
y: 0,
width: 13,
height: 2,
};

terminal
.draw(|f| {
f.render_widget(block, area);
})
.unwrap();

terminal.backend().assert_buffer(&expected);
};

// title top-left with all borders
test_case(
Alignment::Left,
Borders::ALL,
Buffer::with_lines(vec![" ┌Title──────┐ ", " └───────────┘ "]),
);

// title top-left without top border
test_case(
Alignment::Left,
Borders::LEFT | Borders::BOTTOM | Borders::RIGHT,
Buffer::with_lines(vec![" │Title │ ", " └───────────┘ "]),
);

// title top-left with no left border
test_case(
Alignment::Left,
Borders::TOP | Borders::RIGHT | Borders::BOTTOM,
Buffer::with_lines(vec![" Title───────┐ ", " ────────────┘ "]),
);

// title top-left without right border
test_case(
Alignment::Left,
Borders::LEFT | Borders::TOP | Borders::BOTTOM,
Buffer::with_lines(vec![" ┌Title─────── ", " └──────────── "]),
);

// title top-left without borders
test_case(
Alignment::Left,
Borders::NONE,
Buffer::with_lines(vec![" Title ", " "]),
);

// title center with all borders
test_case(
Alignment::Center,
Borders::ALL,
Buffer::with_lines(vec![" ┌───Title───┐ ", " └───────────┘ "]),
);

// title center without top border
test_case(
Alignment::Center,
Borders::LEFT | Borders::BOTTOM | Borders::RIGHT,
Buffer::with_lines(vec![" │ Title │ ", " └───────────┘ "]),
);

// title center with no left border
test_case(
Alignment::Center,
Borders::TOP | Borders::RIGHT | Borders::BOTTOM,
Buffer::with_lines(vec![" ────Title───┐ ", " ────────────┘ "]),
);

// title center without right border
test_case(
Alignment::Center,
Borders::LEFT | Borders::TOP | Borders::BOTTOM,
Buffer::with_lines(vec![" ┌───Title──── ", " └──────────── "]),
);

// title center without borders
test_case(
Alignment::Center,
Borders::NONE,
Buffer::with_lines(vec![" Title ", " "]),
);

// title top-right with all borders
test_case(
Alignment::Right,
Borders::ALL,
Buffer::with_lines(vec![" ┌──────Title┐ ", " └───────────┘ "]),
);

// title top-right without top border
test_case(
Alignment::Right,
Borders::LEFT | Borders::BOTTOM | Borders::RIGHT,
Buffer::with_lines(vec![" │ Title│ ", " └───────────┘ "]),
);

// title top-right with no left border
test_case(
Alignment::Right,
Borders::TOP | Borders::RIGHT | Borders::BOTTOM,
Buffer::with_lines(vec![" ───────Title┐ ", " ────────────┘ "]),
);

// title top-right without right border
test_case(
Alignment::Right,
Borders::LEFT | Borders::TOP | Borders::BOTTOM,
Buffer::with_lines(vec![" ┌───────Title ", " └──────────── "]),
);

// title top-right without borders
test_case(
Alignment::Right,
Borders::NONE,
Buffer::with_lines(vec![" Title ", " "]),
);
}