mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-30 14:32:01 +00:00
fix(widgets): fix centered block title truncation (#1973)
Previously block titles that were aligned center were truncated poorly (aligned to the left, and the last non-fitting title would be truncated on the left and right. This now truncates the titles more obviously centered.
This commit is contained in:
parent
08b21fa55c
commit
5fa342cc52
@ -933,41 +933,74 @@ impl Block<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render titles in the center of the block
|
/// Render titles in the center of the block
|
||||||
///
|
|
||||||
/// Currently this method aligns the titles to the left inside a centered area. This is not
|
|
||||||
/// ideal and should be fixed in the future to align the titles to the center of the block and
|
|
||||||
/// truncate both sides of the titles if the block is too small to fit all titles.
|
|
||||||
#[expect(clippy::similar_names)]
|
|
||||||
fn render_center_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
|
fn render_center_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
|
||||||
|
let area = self.titles_area(area, position);
|
||||||
let titles = self
|
let titles = self
|
||||||
.filtered_titles(position, Alignment::Center)
|
.filtered_titles(position, Alignment::Center)
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
// titles are rendered with a space after each title except the last one
|
||||||
let total_width = titles
|
let total_width = titles
|
||||||
.iter()
|
.iter()
|
||||||
.map(|title| title.width() as u16 + 1) // space between titles
|
.map(|title| title.width() as u16 + 1)
|
||||||
.sum::<u16>()
|
.sum::<u16>()
|
||||||
.saturating_sub(1); // no space for the last title
|
.saturating_sub(1);
|
||||||
|
|
||||||
let titles_area = self.titles_area(area, position);
|
if total_width <= area.width {
|
||||||
let mut titles_area = Rect {
|
self.render_centered_titles_without_truncation(titles, total_width, area, buf);
|
||||||
x: titles_area.left() + (titles_area.width.saturating_sub(total_width) / 2),
|
} else {
|
||||||
..titles_area
|
self.render_centered_titles_with_truncation(titles, total_width, area, buf);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_centered_titles_without_truncation(
|
||||||
|
&self,
|
||||||
|
titles: Vec<&Line<'_>>,
|
||||||
|
total_width: u16,
|
||||||
|
area: Rect,
|
||||||
|
buf: &mut Buffer,
|
||||||
|
) {
|
||||||
|
// titles fit in the area, center them
|
||||||
|
let x = area.left() + area.width.saturating_sub(total_width) / 2;
|
||||||
|
let mut area = Rect { x, ..area };
|
||||||
for title in titles {
|
for title in titles {
|
||||||
if titles_area.is_empty() {
|
let width = title.width() as u16;
|
||||||
break;
|
let title_area = Rect { width, ..area };
|
||||||
}
|
|
||||||
let title_width = title.width() as u16;
|
|
||||||
let title_area = Rect {
|
|
||||||
width: title_width.min(titles_area.width),
|
|
||||||
..titles_area
|
|
||||||
};
|
|
||||||
buf.set_style(title_area, self.titles_style);
|
buf.set_style(title_area, self.titles_style);
|
||||||
title.render(title_area, buf);
|
title.render(title_area, buf);
|
||||||
|
// Move the rendering cursor to the right, leaving 1 column space.
|
||||||
|
area.x = area.x.saturating_add(width + 1);
|
||||||
|
area.width = area.width.saturating_sub(width + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// bump the titles area to the right and reduce its width
|
fn render_centered_titles_with_truncation(
|
||||||
titles_area.x = titles_area.x.saturating_add(title_width + 1);
|
&self,
|
||||||
titles_area.width = titles_area.width.saturating_sub(title_width + 1);
|
titles: Vec<&Line<'_>>,
|
||||||
|
total_width: u16,
|
||||||
|
mut area: Rect,
|
||||||
|
buf: &mut Buffer,
|
||||||
|
) {
|
||||||
|
// titles do not fit in the area, truncate the left side using an offset. The right side
|
||||||
|
// is truncated by the area width.
|
||||||
|
let mut offset = total_width.saturating_sub(area.width) / 2;
|
||||||
|
for title in titles {
|
||||||
|
if area.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let width = area.width.min(title.width() as u16).saturating_sub(offset);
|
||||||
|
let title_area = Rect { width, ..area };
|
||||||
|
buf.set_style(title_area, self.titles_style);
|
||||||
|
if offset > 0 {
|
||||||
|
// truncate the left side of the title to fit the area
|
||||||
|
title.clone().right_aligned().render(title_area, buf);
|
||||||
|
offset = offset.saturating_sub(width).saturating_sub(1);
|
||||||
|
} else {
|
||||||
|
// truncate the right side of the title to fit the area if needed
|
||||||
|
title.clone().left_aligned().render(title_area, buf);
|
||||||
|
}
|
||||||
|
// Leave 1 column of spacing between titles.
|
||||||
|
area.x = area.x.saturating_add(width + 1);
|
||||||
|
area.width = area.width.saturating_sub(width + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1936,6 +1969,16 @@ mod tests {
|
|||||||
pretty_assertions::assert_eq!(Buffer::with_lines(expected.lines()), buffer);
|
pretty_assertions::assert_eq!(Buffer::with_lines(expected.lines()), buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn left_titles() {
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||||
|
Block::new()
|
||||||
|
.title("L12")
|
||||||
|
.title("L34")
|
||||||
|
.render(buffer.area, &mut buffer);
|
||||||
|
assert_eq!(buffer, Buffer::with_lines(["L12 L34 "]));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn left_titles_truncated() {
|
fn left_titles_truncated() {
|
||||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||||
@ -1946,12 +1989,16 @@ mod tests {
|
|||||||
assert_eq!(buffer, Buffer::with_lines(["L12345 L67"]));
|
assert_eq!(buffer, Buffer::with_lines(["L12345 L67"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Note: this test is probably not what you'd expect, but it is how it works in the current
|
#[test]
|
||||||
/// implementation. Update this if the behavior changes.
|
fn center_titles() {
|
||||||
///
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||||
/// This probably should render the titles centered as a whole and then truncate both titles
|
Block::new()
|
||||||
/// to fit, but instead it renders each title and truncates them individually. This causes the
|
.title(Line::from("C12").centered())
|
||||||
/// left title to be displayed in full, while the right title is truncated.
|
.title(Line::from("C34").centered())
|
||||||
|
.render(buffer.area, &mut buffer);
|
||||||
|
assert_eq!(buffer, Buffer::with_lines([" C12 C34 "]));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn center_titles_truncated() {
|
fn center_titles_truncated() {
|
||||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||||
@ -1959,7 +2006,17 @@ mod tests {
|
|||||||
.title(Line::from("C12345").centered())
|
.title(Line::from("C12345").centered())
|
||||||
.title(Line::from("C67890").centered())
|
.title(Line::from("C67890").centered())
|
||||||
.render(buffer.area, &mut buffer);
|
.render(buffer.area, &mut buffer);
|
||||||
assert_eq!(buffer, Buffer::with_lines(["C12345 678"]));
|
assert_eq!(buffer, Buffer::with_lines(["12345 C678"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn right_titles() {
|
||||||
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||||
|
Block::new()
|
||||||
|
.title(Line::from("R12").right_aligned())
|
||||||
|
.title(Line::from("R34").right_aligned())
|
||||||
|
.render(buffer.area, &mut buffer);
|
||||||
|
assert_eq!(buffer, Buffer::with_lines([" R12 R34"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user