Skip to content

Commit 5fa342c

Browse files
authored
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.
1 parent 08b21fa commit 5fa342c

1 file changed

Lines changed: 87 additions & 30 deletions

File tree

ratatui-widgets/src/block.rs

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -933,41 +933,74 @@ impl Block<'_> {
933933
}
934934

935935
/// Render titles in the center of the block
936-
///
937-
/// Currently this method aligns the titles to the left inside a centered area. This is not
938-
/// ideal and should be fixed in the future to align the titles to the center of the block and
939-
/// truncate both sides of the titles if the block is too small to fit all titles.
940-
#[expect(clippy::similar_names)]
941936
fn render_center_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
937+
let area = self.titles_area(area, position);
942938
let titles = self
943939
.filtered_titles(position, Alignment::Center)
944940
.collect_vec();
941+
// titles are rendered with a space after each title except the last one
945942
let total_width = titles
946943
.iter()
947-
.map(|title| title.width() as u16 + 1) // space between titles
944+
.map(|title| title.width() as u16 + 1)
948945
.sum::<u16>()
949-
.saturating_sub(1); // no space for the last title
946+
.saturating_sub(1);
947+
948+
if total_width <= area.width {
949+
self.render_centered_titles_without_truncation(titles, total_width, area, buf);
950+
} else {
951+
self.render_centered_titles_with_truncation(titles, total_width, area, buf);
952+
}
953+
}
950954

951-
let titles_area = self.titles_area(area, position);
952-
let mut titles_area = Rect {
953-
x: titles_area.left() + (titles_area.width.saturating_sub(total_width) / 2),
954-
..titles_area
955-
};
955+
fn render_centered_titles_without_truncation(
956+
&self,
957+
titles: Vec<&Line<'_>>,
958+
total_width: u16,
959+
area: Rect,
960+
buf: &mut Buffer,
961+
) {
962+
// titles fit in the area, center them
963+
let x = area.left() + area.width.saturating_sub(total_width) / 2;
964+
let mut area = Rect { x, ..area };
956965
for title in titles {
957-
if titles_area.is_empty() {
958-
break;
959-
}
960-
let title_width = title.width() as u16;
961-
let title_area = Rect {
962-
width: title_width.min(titles_area.width),
963-
..titles_area
964-
};
966+
let width = title.width() as u16;
967+
let title_area = Rect { width, ..area };
965968
buf.set_style(title_area, self.titles_style);
966969
title.render(title_area, buf);
970+
// Move the rendering cursor to the right, leaving 1 column space.
971+
area.x = area.x.saturating_add(width + 1);
972+
area.width = area.width.saturating_sub(width + 1);
973+
}
974+
}
967975

968-
// bump the titles area to the right and reduce its width
969-
titles_area.x = titles_area.x.saturating_add(title_width + 1);
970-
titles_area.width = titles_area.width.saturating_sub(title_width + 1);
976+
fn render_centered_titles_with_truncation(
977+
&self,
978+
titles: Vec<&Line<'_>>,
979+
total_width: u16,
980+
mut area: Rect,
981+
buf: &mut Buffer,
982+
) {
983+
// titles do not fit in the area, truncate the left side using an offset. The right side
984+
// is truncated by the area width.
985+
let mut offset = total_width.saturating_sub(area.width) / 2;
986+
for title in titles {
987+
if area.is_empty() {
988+
break;
989+
}
990+
let width = area.width.min(title.width() as u16).saturating_sub(offset);
991+
let title_area = Rect { width, ..area };
992+
buf.set_style(title_area, self.titles_style);
993+
if offset > 0 {
994+
// truncate the left side of the title to fit the area
995+
title.clone().right_aligned().render(title_area, buf);
996+
offset = offset.saturating_sub(width).saturating_sub(1);
997+
} else {
998+
// truncate the right side of the title to fit the area if needed
999+
title.clone().left_aligned().render(title_area, buf);
1000+
}
1001+
// Leave 1 column of spacing between titles.
1002+
area.x = area.x.saturating_add(width + 1);
1003+
area.width = area.width.saturating_sub(width + 1);
9711004
}
9721005
}
9731006

@@ -1936,6 +1969,16 @@ mod tests {
19361969
pretty_assertions::assert_eq!(Buffer::with_lines(expected.lines()), buffer);
19371970
}
19381971

1972+
#[test]
1973+
fn left_titles() {
1974+
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
1975+
Block::new()
1976+
.title("L12")
1977+
.title("L34")
1978+
.render(buffer.area, &mut buffer);
1979+
assert_eq!(buffer, Buffer::with_lines(["L12 L34 "]));
1980+
}
1981+
19391982
#[test]
19401983
fn left_titles_truncated() {
19411984
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
@@ -1946,20 +1989,34 @@ mod tests {
19461989
assert_eq!(buffer, Buffer::with_lines(["L12345 L67"]));
19471990
}
19481991

1949-
/// Note: this test is probably not what you'd expect, but it is how it works in the current
1950-
/// implementation. Update this if the behavior changes.
1951-
///
1952-
/// This probably should render the titles centered as a whole and then truncate both titles
1953-
/// to fit, but instead it renders each title and truncates them individually. This causes the
1954-
/// left title to be displayed in full, while the right title is truncated.
1992+
#[test]
1993+
fn center_titles() {
1994+
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
1995+
Block::new()
1996+
.title(Line::from("C12").centered())
1997+
.title(Line::from("C34").centered())
1998+
.render(buffer.area, &mut buffer);
1999+
assert_eq!(buffer, Buffer::with_lines([" C12 C34 "]));
2000+
}
2001+
19552002
#[test]
19562003
fn center_titles_truncated() {
19572004
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
19582005
Block::new()
19592006
.title(Line::from("C12345").centered())
19602007
.title(Line::from("C67890").centered())
19612008
.render(buffer.area, &mut buffer);
1962-
assert_eq!(buffer, Buffer::with_lines(["C12345 678"]));
2009+
assert_eq!(buffer, Buffer::with_lines(["12345 C678"]));
2010+
}
2011+
2012+
#[test]
2013+
fn right_titles() {
2014+
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
2015+
Block::new()
2016+
.title(Line::from("R12").right_aligned())
2017+
.title(Line::from("R34").right_aligned())
2018+
.render(buffer.area, &mut buffer);
2019+
assert_eq!(buffer, Buffer::with_lines([" R12 R34"]));
19632020
}
19642021

19652022
#[test]

0 commit comments

Comments
 (0)