mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-10 07:04:17 +00:00
feat(block): support for having more than one title (#232)
This commit is contained in:
parent
e869869462
commit
a04b190251
7 changed files with 510 additions and 82 deletions
|
@ -10,7 +10,7 @@ use ratatui::{
|
|||
layout::{Alignment, Constraint, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
text::Span,
|
||||
widgets::{Block, BorderType, Borders, Padding, Paragraph},
|
||||
widgets::{block::title::Title, Block, BorderType, Borders, Padding, Paragraph},
|
||||
Frame, Terminal,
|
||||
};
|
||||
|
||||
|
@ -62,8 +62,7 @@ fn ui<B: Backend>(f: &mut Frame<B>) {
|
|||
// Surrounding block
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("Main block with round corners")
|
||||
.title_alignment(Alignment::Center)
|
||||
.title(Title::from("Main block with round corners").alignment(Alignment::Center))
|
||||
.border_type(BorderType::Rounded);
|
||||
f.render_widget(block, size);
|
||||
|
||||
|
@ -89,15 +88,16 @@ fn ui<B: Backend>(f: &mut Frame<B>) {
|
|||
f.render_widget(block, top_chunks[0]);
|
||||
|
||||
// Top right inner block with styled title aligned to the right
|
||||
let block = Block::default()
|
||||
.title(Span::styled(
|
||||
let block = Block::default().title(
|
||||
Title::from(Span::styled(
|
||||
"Styled title",
|
||||
Style::default()
|
||||
.fg(Color::White)
|
||||
.bg(Color::Red)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
))
|
||||
.title_alignment(Alignment::Right);
|
||||
.alignment(Alignment::Right),
|
||||
);
|
||||
f.render_widget(block, top_chunks[1]);
|
||||
|
||||
// Bottom two inner blocks
|
||||
|
|
|
@ -14,7 +14,7 @@ use ratatui::{
|
|||
style::{Color, Modifier, Style},
|
||||
symbols,
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Gauge, LineGauge, List, ListItem, Paragraph, Widget},
|
||||
widgets::{block::title::Title, Block, Gauge, LineGauge, List, ListItem, Paragraph, Widget},
|
||||
Frame, Terminal, TerminalOptions, Viewport,
|
||||
};
|
||||
|
||||
|
@ -227,9 +227,7 @@ fn run_app<B: Backend>(
|
|||
fn ui<B: Backend>(f: &mut Frame<B>, downloads: &Downloads) {
|
||||
let size = f.size();
|
||||
|
||||
let block = Block::default()
|
||||
.title("Progress")
|
||||
.title_alignment(Alignment::Center);
|
||||
let block = Block::default().title(Title::from("Progress").alignment(Alignment::Center));
|
||||
f.render_widget(block, size);
|
||||
|
||||
let chunks = Layout::default()
|
||||
|
|
|
@ -62,7 +62,7 @@ pub struct Margin {
|
|||
pub horizontal: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Alignment {
|
||||
Left,
|
||||
Center,
|
||||
|
|
57
src/title.rs
Normal file
57
src/title.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use crate::{layout::Alignment, text::Line};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Title<'a> {
|
||||
pub content: Line<'a>,
|
||||
/// Defaults to Left if unset
|
||||
pub alignment: Option<Alignment>,
|
||||
|
||||
/// Defaults to Top if unset
|
||||
pub position: Option<Position>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Position {
|
||||
#[default]
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl<'a> Title<'a> {
|
||||
pub fn content<T>(mut self, content: T) -> Title<'a>
|
||||
where
|
||||
T: Into<Line<'a>>,
|
||||
{
|
||||
self.content = content.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alignment(mut self, alignment: Alignment) -> Title<'a> {
|
||||
self.alignment = Some(alignment);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn position(mut self, position: Position) -> Title<'a> {
|
||||
self.position = Some(position);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<T> for Title<'a>
|
||||
where
|
||||
T: Into<Line<'a>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self::default().content(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for Title<'a> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
content: Line::from(""),
|
||||
alignment: Some(Alignment::Left),
|
||||
position: Some(Position::Top),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
#[path = "../title.rs"]
|
||||
pub mod title;
|
||||
|
||||
use crate::{
|
||||
buffer::Buffer,
|
||||
layout::{Alignment, Rect},
|
||||
style::Style,
|
||||
symbols::line,
|
||||
text::{Line, Span},
|
||||
widgets::{Borders, Widget},
|
||||
};
|
||||
|
||||
use self::title::{Position, Title};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BorderType {
|
||||
Plain,
|
||||
|
@ -96,22 +100,38 @@ impl Padding {
|
|||
/// .border_type(BorderType::Rounded)
|
||||
/// .style(Style::default().bg(Color::Black));
|
||||
/// ```
|
||||
///
|
||||
/// You may also use multiple titles like in the following:
|
||||
/// ```
|
||||
/// # use ratatui::widgets::{Block, BorderType, Borders, block::title::{Position, Title}};
|
||||
/// # use ratatui::style::{Style, Color};
|
||||
/// Block::default()
|
||||
/// .title("Title 1")
|
||||
/// .title(Title::from("Title 2").position(Position::Bottom))
|
||||
/// .borders(Borders::LEFT | Borders::RIGHT)
|
||||
/// .border_style(Style::default().fg(Color::White))
|
||||
/// .border_type(BorderType::Rounded)
|
||||
/// .style(Style::default().bg(Color::Black));
|
||||
/// ```
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Block<'a> {
|
||||
/// Optional title place on the upper left of the block
|
||||
title: Option<Line<'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,
|
||||
/// Whether or not title goes on top or bottom row of the block
|
||||
title_on_bottom: bool,
|
||||
/// List of titles
|
||||
titles: Vec<Title<'a>>,
|
||||
/// The style to be patched to all titles of the block
|
||||
titles_style: Style,
|
||||
/// The default alignment of the titles that don't have one
|
||||
titles_alignment: Alignment,
|
||||
/// The default position of the titles that don't have one
|
||||
titles_position: Position,
|
||||
|
||||
/// Visible borders
|
||||
borders: Borders,
|
||||
/// Border style
|
||||
border_style: Style,
|
||||
/// Type of the border. The default is plain lines but one can choose to have rounded corners
|
||||
/// or doubled lines instead.
|
||||
/// Type of the border. The default is plain lines but one can choose to have rounded or doubled lines instead.
|
||||
border_type: BorderType,
|
||||
|
||||
/// Widget style
|
||||
style: Style,
|
||||
/// Block padding
|
||||
|
@ -121,9 +141,10 @@ pub struct Block<'a> {
|
|||
impl<'a> Default for Block<'a> {
|
||||
fn default() -> Block<'a> {
|
||||
Block {
|
||||
title: None,
|
||||
title_alignment: Alignment::Left,
|
||||
title_on_bottom: false,
|
||||
titles: Vec::new(),
|
||||
titles_style: Style::default(),
|
||||
titles_alignment: Alignment::Left,
|
||||
titles_position: Position::default(),
|
||||
borders: Borders::NONE,
|
||||
border_style: Style::default(),
|
||||
border_type: BorderType::Plain,
|
||||
|
@ -134,33 +155,81 @@ impl<'a> Default for Block<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Block<'a> {
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use ratatui::widgets::{Block, block::title::Title};
|
||||
/// # use ratatui::layout::Alignment;
|
||||
/// Block::default()
|
||||
/// .title("Title") // By default in the top right corner
|
||||
/// .title(Title::from("Left").alignment(Alignment::Left))
|
||||
/// .title(
|
||||
/// Title::from("Center")
|
||||
/// .alignment(Alignment::Center),
|
||||
/// );
|
||||
///```
|
||||
/// Adds a title to the block.
|
||||
///
|
||||
/// The `title` function allows you to add a title to the block. You can call this function multiple times to add multiple titles.
|
||||
///
|
||||
/// Each title will be rendered with a single space separating titles that are in the same position or alignment. When both centered and non-centered titles are rendered, the centered space is calculated based on the full width of the block, rather than the leftover width.
|
||||
///
|
||||
/// You can provide various types as the title, including strings, string slices, borrowed strings (`Cow<str>`), spans, or vectors of spans (`Vec<Span>`).
|
||||
///
|
||||
/// By default, the titles will avoid being rendered in the corners of the block but will align against the left or right edge of the block if there is no border on that edge.
|
||||
///
|
||||
/// Note: If the block is too small and multiple titles overlap, the border might get cut off at a corner.
|
||||
pub fn title<T>(mut self, title: T) -> Block<'a>
|
||||
where
|
||||
T: Into<Line<'a>>,
|
||||
T: Into<Title<'a>>,
|
||||
{
|
||||
self.title = Some(title.into());
|
||||
self.titles.push(title.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
since = "0.10.0",
|
||||
note = "You should use styling capabilities of `text::Line` given as argument of the `title` method to apply styling to the title."
|
||||
)]
|
||||
/// Applies the style to all titles. If a title already has a style, it will add on top of it.
|
||||
pub fn title_style(mut self, style: Style) -> Block<'a> {
|
||||
if let Some(t) = self.title {
|
||||
let title = String::from(t);
|
||||
self.title = Some(Line::from(Span::styled(title, style)));
|
||||
}
|
||||
self.titles_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Aligns all elements that don't have an alignment
|
||||
/// # Example
|
||||
/// This example aligns all titles in the center except "right" title
|
||||
/// ```
|
||||
/// # use ratatui::widgets::{Block, block::title::Title};
|
||||
/// # use ratatui::layout::Alignment;
|
||||
/// Block::default()
|
||||
/// // This title won't be aligned in the center
|
||||
/// .title(Title::from("right").alignment(Alignment::Right))
|
||||
/// .title("foo")
|
||||
/// .title("bar")
|
||||
/// .title_alignment(Alignment::Center);
|
||||
/// ```
|
||||
pub fn title_alignment(mut self, alignment: Alignment) -> Block<'a> {
|
||||
self.title_alignment = alignment;
|
||||
self.titles_alignment = alignment;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn title_on_bottom(mut self) -> Block<'a> {
|
||||
self.title_on_bottom = true;
|
||||
#[deprecated(since = "0.22.0", note = "You should use a `title_position` instead.")]
|
||||
/// This method just calls `title_position` with Position::Bottom
|
||||
pub fn title_on_bottom(self) -> Block<'a> {
|
||||
self.title_position(Position::Bottom)
|
||||
}
|
||||
|
||||
/// Positions all titles that don't have a position
|
||||
/// # Example
|
||||
/// This example position all titles on the bottom except "top" title
|
||||
/// ```
|
||||
/// # use ratatui::widgets::{Block, BorderType, Borders, block::title::{Position, Title}};
|
||||
/// Block::default()
|
||||
/// // This title won't be aligned in the center
|
||||
/// .title(Title::from("top").position(Position::Top))
|
||||
/// .title("foo")
|
||||
/// .title("bar")
|
||||
/// .title_position(Position::Bottom);
|
||||
/// ```
|
||||
pub fn title_position(mut self, position: Position) -> Block<'a> {
|
||||
self.titles_position = position;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -219,7 +288,7 @@ impl<'a> Block<'a> {
|
|||
inner.x = inner.x.saturating_add(1).min(inner.right());
|
||||
inner.width = inner.width.saturating_sub(1);
|
||||
}
|
||||
if self.borders.intersects(Borders::TOP) || self.title.is_some() {
|
||||
if self.borders.intersects(Borders::TOP) || !self.titles.is_empty() {
|
||||
inner.y = inner.y.saturating_add(1).min(inner.bottom());
|
||||
inner.height = inner.height.saturating_sub(1);
|
||||
}
|
||||
|
@ -247,13 +316,8 @@ impl<'a> Block<'a> {
|
|||
self.padding = padding;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Block<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if area.area() == 0 {
|
||||
return;
|
||||
}
|
||||
fn render_borders(&self, area: Rect, buf: &mut Buffer) {
|
||||
buf.set_style(area, self.style);
|
||||
let symbols = BorderType::line_symbols(self.border_type);
|
||||
|
||||
|
@ -310,9 +374,22 @@ impl<'a> Widget for Block<'a> {
|
|||
.set_symbol(symbols.top_left)
|
||||
.set_style(self.border_style);
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
if let Some(title) = self.title {
|
||||
/* Titles Rendering */
|
||||
fn get_title_y(&self, position: Position, area: Rect) -> u16 {
|
||||
match position {
|
||||
Position::Bottom => area.bottom() - 1,
|
||||
Position::Top => area.top(),
|
||||
}
|
||||
}
|
||||
|
||||
fn title_filter(&self, title: &Title, alignment: Alignment, position: Position) -> bool {
|
||||
title.alignment.unwrap_or(self.titles_alignment) == alignment
|
||||
&& title.position.unwrap_or(self.titles_position) == position
|
||||
}
|
||||
|
||||
fn calculate_title_area_offsets(&self, area: Rect) -> (u16, u16, u16) {
|
||||
let left_border_dx = u16::from(self.borders.intersects(Borders::LEFT));
|
||||
let right_border_dx = u16::from(self.borders.intersects(Borders::RIGHT));
|
||||
|
||||
|
@ -321,24 +398,96 @@ impl<'a> Widget for Block<'a> {
|
|||
.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 = if self.title_on_bottom {
|
||||
area.bottom() - 1
|
||||
} else {
|
||||
area.top()
|
||||
};
|
||||
|
||||
buf.set_line(title_x, title_y, &title, title_area_width);
|
||||
(left_border_dx, right_border_dx, title_area_width)
|
||||
}
|
||||
|
||||
fn render_left_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
|
||||
let (left_border_dx, _, title_area_width) = self.calculate_title_area_offsets(area);
|
||||
|
||||
let mut current_offset = left_border_dx;
|
||||
self.titles
|
||||
.iter()
|
||||
.filter(|title| self.title_filter(title, Alignment::Left, position))
|
||||
.for_each(|title| {
|
||||
let title_x = current_offset;
|
||||
current_offset += title.content.width() as u16 + 1;
|
||||
|
||||
buf.set_line(
|
||||
title_x + area.left(),
|
||||
self.get_title_y(position, area),
|
||||
&title.content,
|
||||
title_area_width,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn render_center_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
|
||||
let (_, _, title_area_width) = self.calculate_title_area_offsets(area);
|
||||
|
||||
let titles = self
|
||||
.titles
|
||||
.iter()
|
||||
.filter(|title| self.title_filter(title, Alignment::Center, position));
|
||||
|
||||
let titles_sum = titles
|
||||
.clone()
|
||||
.fold(-1, |acc, f| acc + f.content.width() as i16 + 1); // First element isn't spaced
|
||||
|
||||
let mut current_offset = area.width.saturating_sub(titles_sum as u16) / 2;
|
||||
titles.for_each(|title| {
|
||||
let title_x = current_offset;
|
||||
current_offset += title.content.width() as u16 + 1;
|
||||
|
||||
buf.set_line(
|
||||
title_x + area.left(),
|
||||
self.get_title_y(position, area),
|
||||
&title.content,
|
||||
title_area_width,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn render_right_titles(&self, position: Position, area: Rect, buf: &mut Buffer) {
|
||||
let (_, right_border_dx, title_area_width) = self.calculate_title_area_offsets(area);
|
||||
|
||||
let mut current_offset = right_border_dx;
|
||||
self.titles
|
||||
.iter()
|
||||
.filter(|title| self.title_filter(title, Alignment::Right, position))
|
||||
.rev() // so that the titles appear in the order they have been set
|
||||
.for_each(|title| {
|
||||
current_offset += title.content.width() as u16 + 1;
|
||||
let title_x = current_offset - 1; // First element isn't spaced
|
||||
|
||||
buf.set_line(
|
||||
area.width.saturating_sub(title_x) + area.left(),
|
||||
self.get_title_y(position, area),
|
||||
&title.content,
|
||||
title_area_width,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn render_title_position(&self, position: Position, area: Rect, buf: &mut Buffer) {
|
||||
// Note: the order in which these functions are called define the overlapping behavior
|
||||
self.render_right_titles(position, area, buf);
|
||||
self.render_center_titles(position, area, buf);
|
||||
self.render_left_titles(position, area, buf);
|
||||
}
|
||||
|
||||
fn render_titles(&self, area: Rect, buf: &mut Buffer) {
|
||||
self.render_title_position(Position::Top, area, buf);
|
||||
self.render_title_position(Position::Bottom, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Block<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if area.area() == 0 {
|
||||
return;
|
||||
}
|
||||
self.render_borders(area, buf);
|
||||
self.render_titles(area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -642,8 +791,7 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.title("Test")
|
||||
.title_alignment(Alignment::Center)
|
||||
.title(Title::from("Test").alignment(Alignment::Center))
|
||||
.inner(Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
@ -659,8 +807,7 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
Block::default()
|
||||
.title("Test")
|
||||
.title_alignment(Alignment::Right)
|
||||
.title(Title::from("Test").alignment(Alignment::Right))
|
||||
.inner(Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
//! - [`Clear`]
|
||||
|
||||
mod barchart;
|
||||
mod block;
|
||||
pub mod block;
|
||||
#[cfg(feature = "widget-calendar")]
|
||||
pub mod calendar;
|
||||
pub mod canvas;
|
||||
|
|
|
@ -4,7 +4,10 @@ use ratatui::{
|
|||
layout::{Alignment, Rect},
|
||||
style::{Color, Style},
|
||||
text::Span,
|
||||
widgets::{Block, Borders},
|
||||
widgets::{
|
||||
block::title::{Position, Title},
|
||||
Block, Borders,
|
||||
},
|
||||
Terminal,
|
||||
};
|
||||
|
||||
|
@ -46,6 +49,80 @@ fn widgets_block_renders() {
|
|||
terminal.backend().assert_buffer(&expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_block_titles_overlap() {
|
||||
let test_case = |block, area: Rect, expected| {
|
||||
let backend = TestBackend::new(area.width, area.height);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|f| {
|
||||
f.render_widget(block, area);
|
||||
})
|
||||
.unwrap();
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
};
|
||||
|
||||
// Left overrides the center
|
||||
test_case(
|
||||
Block::default()
|
||||
.title(Title::from("aaaaa").alignment(Alignment::Left))
|
||||
.title(Title::from("bbb").alignment(Alignment::Center))
|
||||
.title(Title::from("ccc").alignment(Alignment::Right)),
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 10,
|
||||
height: 1,
|
||||
},
|
||||
Buffer::with_lines(vec!["aaaaab ccc"]),
|
||||
);
|
||||
|
||||
// Left alignment overrides the center alignment which overrides the right alignment
|
||||
test_case(
|
||||
Block::default()
|
||||
.title(Title::from("aaaaa").alignment(Alignment::Left))
|
||||
.title(Title::from("bbbbb").alignment(Alignment::Center))
|
||||
.title(Title::from("ccccc").alignment(Alignment::Right)),
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 11,
|
||||
height: 1,
|
||||
},
|
||||
Buffer::with_lines(vec!["aaaaabbbccc"]),
|
||||
);
|
||||
|
||||
// Multiple left alignment overrides the center alignment and the right alignment
|
||||
test_case(
|
||||
Block::default()
|
||||
.title(Title::from("aaaaa").alignment(Alignment::Left))
|
||||
.title(Title::from("aaaaa").alignment(Alignment::Left))
|
||||
.title(Title::from("bbbbb").alignment(Alignment::Center))
|
||||
.title(Title::from("ccccc").alignment(Alignment::Right)),
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 11,
|
||||
height: 1,
|
||||
},
|
||||
Buffer::with_lines(vec!["aaaaabaaaaa"]),
|
||||
);
|
||||
|
||||
// The right alignment doesn't override the center alignment, but pierces through it
|
||||
test_case(
|
||||
Block::default()
|
||||
.title(Title::from("bbbbb").alignment(Alignment::Center))
|
||||
.title(Title::from("ccccccccccc").alignment(Alignment::Right)),
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 11,
|
||||
height: 1,
|
||||
},
|
||||
Buffer::with_lines(vec!["cccbbbbbccc"]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_block_renders_on_small_areas() {
|
||||
let test_case = |block, area: Rect, expected| {
|
||||
|
@ -219,8 +296,7 @@ fn widgets_block_title_alignment() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
let block = Block::default()
|
||||
.title(Span::styled("Title", Style::default()))
|
||||
.title_alignment(alignment)
|
||||
.title(Title::from(Span::styled("Title", Style::default())).alignment(alignment))
|
||||
.borders(borders);
|
||||
|
||||
let area = Rect {
|
||||
|
@ -352,9 +428,11 @@ fn widgets_block_title_alignment_bottom() {
|
|||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
let block = Block::default()
|
||||
.title(Span::styled("Title", Style::default()))
|
||||
.title_alignment(alignment)
|
||||
.title_on_bottom()
|
||||
.title(
|
||||
Title::from(Span::styled("Title", Style::default()))
|
||||
.alignment(alignment)
|
||||
.position(Position::Bottom),
|
||||
)
|
||||
.borders(borders);
|
||||
|
||||
let area = Rect {
|
||||
|
@ -478,3 +556,151 @@ fn widgets_block_title_alignment_bottom() {
|
|||
Buffer::with_lines(vec![" ", " Title "]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widgets_block_multiple_titles() {
|
||||
let test_case = |title_a, title_b, borders, expected| {
|
||||
let backend = TestBackend::new(15, 2);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
let block = Block::default()
|
||||
.title(title_a)
|
||||
.title(title_b)
|
||||
.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 bottom-left with all borders
|
||||
test_case(
|
||||
Title::from("foo"),
|
||||
Title::from("bar"),
|
||||
Borders::ALL,
|
||||
Buffer::with_lines(vec![" ┌foo─bar────┐ ", " └───────────┘ "]),
|
||||
);
|
||||
|
||||
// title top-left without top border
|
||||
test_case(
|
||||
Title::from("foo"),
|
||||
Title::from("bar"),
|
||||
Borders::LEFT | Borders::BOTTOM | Borders::RIGHT,
|
||||
Buffer::with_lines(vec![" │foo bar │ ", " └───────────┘ "]),
|
||||
);
|
||||
|
||||
// title top-left with no left border
|
||||
test_case(
|
||||
Title::from("foo"),
|
||||
Title::from("bar"),
|
||||
Borders::TOP | Borders::RIGHT | Borders::BOTTOM,
|
||||
Buffer::with_lines(vec![" foo─bar─────┐ ", " ────────────┘ "]),
|
||||
);
|
||||
|
||||
// title top-left without right border
|
||||
test_case(
|
||||
Title::from("foo"),
|
||||
Title::from("bar"),
|
||||
Borders::LEFT | Borders::TOP | Borders::BOTTOM,
|
||||
Buffer::with_lines(vec![" ┌foo─bar───── ", " └──────────── "]),
|
||||
);
|
||||
|
||||
// title top-left without borders
|
||||
test_case(
|
||||
Title::from("foo"),
|
||||
Title::from("bar"),
|
||||
Borders::NONE,
|
||||
Buffer::with_lines(vec![" foo bar ", " "]),
|
||||
);
|
||||
|
||||
// title center with all borders
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Center),
|
||||
Title::from("bar").alignment(Alignment::Center),
|
||||
Borders::ALL,
|
||||
Buffer::with_lines(vec![" ┌──foo─bar──┐ ", " └───────────┘ "]),
|
||||
);
|
||||
|
||||
// title center without top border
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Center),
|
||||
Title::from("bar").alignment(Alignment::Center),
|
||||
Borders::LEFT | Borders::BOTTOM | Borders::RIGHT,
|
||||
Buffer::with_lines(vec![" │ foo bar │ ", " └───────────┘ "]),
|
||||
);
|
||||
|
||||
// title center with no left border
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Center),
|
||||
Title::from("bar").alignment(Alignment::Center),
|
||||
Borders::TOP | Borders::RIGHT | Borders::BOTTOM,
|
||||
Buffer::with_lines(vec![" ───foo─bar──┐ ", " ────────────┘ "]),
|
||||
);
|
||||
|
||||
// title center without right border
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Center),
|
||||
Title::from("bar").alignment(Alignment::Center),
|
||||
Borders::LEFT | Borders::TOP | Borders::BOTTOM,
|
||||
Buffer::with_lines(vec![" ┌──foo─bar─── ", " └──────────── "]),
|
||||
);
|
||||
|
||||
// title center without borders
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Center),
|
||||
Title::from("bar").alignment(Alignment::Center),
|
||||
Borders::NONE,
|
||||
Buffer::with_lines(vec![" foo bar ", " "]),
|
||||
);
|
||||
|
||||
// title top-right with all borders
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Right),
|
||||
Title::from("bar").alignment(Alignment::Right),
|
||||
Borders::ALL,
|
||||
Buffer::with_lines(vec![" ┌────foo─bar┐ ", " └───────────┘ "]),
|
||||
);
|
||||
|
||||
// title top-right without top border
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Right),
|
||||
Title::from("bar").alignment(Alignment::Right),
|
||||
Borders::LEFT | Borders::BOTTOM | Borders::RIGHT,
|
||||
Buffer::with_lines(vec![" │ foo bar│ ", " └───────────┘ "]),
|
||||
);
|
||||
|
||||
// title top-right with no left border
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Right),
|
||||
Title::from("bar").alignment(Alignment::Right),
|
||||
Borders::TOP | Borders::RIGHT | Borders::BOTTOM,
|
||||
Buffer::with_lines(vec![" ─────foo─bar┐ ", " ────────────┘ "]),
|
||||
);
|
||||
|
||||
// title top-right without right border
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Right),
|
||||
Title::from("bar").alignment(Alignment::Right),
|
||||
Borders::LEFT | Borders::TOP | Borders::BOTTOM,
|
||||
Buffer::with_lines(vec![" ┌─────foo─bar ", " └──────────── "]),
|
||||
);
|
||||
|
||||
// title top-right without borders
|
||||
test_case(
|
||||
Title::from("foo").alignment(Alignment::Right),
|
||||
Title::from("bar").alignment(Alignment::Right),
|
||||
Borders::NONE,
|
||||
Buffer::with_lines(vec![" foo bar ", " "]),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue