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 4 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
69 changes: 64 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,35 @@ 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 => 0,
Alignment::Center => (title_area_width - title.width() as u16) / 2,
Copy link
Owner

Choose a reason for hiding this comment

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

This will probably panic if title_area_width < title.width(). You may want to use saturating_sub here as well unless the precondition is checked beforehand. This could use a test as well.

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 will update to use saturating_sub and add a test for this case. Thank you for pointing this out.

Alignment::Right => (title_area_width - title.width() as u16),
};

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

buf.set_spans(title_x, title_y, &title, title_area_width);
}
}
}
Expand Down Expand Up @@ -507,5 +532,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,
},
);
}
}