mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-27 04:50:46 +00:00
feat: add width() impl for tabs (#2049)
The purpose of this is to make it easy for apps to easily calculate the total tab width including all dividers and padding.
This commit is contained in:
parent
719badb5b8
commit
75b78be09f
@ -8,6 +8,7 @@ use ratatui_core::style::{Style, Styled};
|
||||
use ratatui_core::symbols;
|
||||
use ratatui_core::text::{Line, Span};
|
||||
use ratatui_core::widgets::Widget;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::block::{Block, BlockExt};
|
||||
|
||||
@ -446,6 +447,64 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl UnicodeWidthStr for Tabs<'_> {
|
||||
/// Returns the width of the rendered tabs.
|
||||
///
|
||||
/// The width includes the titles, dividers, and padding. It does not include any borders added
|
||||
/// by the optional block.
|
||||
///
|
||||
/// Characters in the Ambiguous category are considered single-width.
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::Tabs;
|
||||
/// use unicode_width::UnicodeWidthStr;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3"]);
|
||||
/// assert_eq!(tabs.width(), 20); // " Tab1 │ Tab2 │ Tab3 "
|
||||
/// ```
|
||||
fn width(&self) -> usize {
|
||||
let titles_width = self.titles.iter().map(Line::width).sum::<usize>();
|
||||
let title_count = self.titles.len();
|
||||
let divider_count = title_count.saturating_sub(1);
|
||||
let divider_width = divider_count.saturating_mul(self.divider.width());
|
||||
let left_padding_width = title_count.saturating_mul(self.padding_left.width());
|
||||
let right_padding_width = title_count.saturating_mul(self.padding_right.width());
|
||||
titles_width + divider_width + left_padding_width + right_padding_width
|
||||
}
|
||||
|
||||
/// Returns the width of the rendered tabs, accounting for CJK characters.
|
||||
///
|
||||
/// This is probably the wrong method to use in most contexts that Ratatui applications care
|
||||
/// about as it doesn't correlate with the visual representation of most terminals. Consider
|
||||
/// using [`Tabs::width`] instead.
|
||||
///
|
||||
/// The width includes the titles, dividers, and padding. It does not include any borders added
|
||||
/// by the optional block.
|
||||
///
|
||||
/// Characters in the Ambiguous category are considered double-width.
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::Tabs;
|
||||
/// use unicode_width::UnicodeWidthStr;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["你", "好", "世界"]);
|
||||
/// assert_eq!("你".width_cjk(), 2);
|
||||
/// assert_eq!("好".width_cjk(), 2);
|
||||
/// assert_eq!("世界".width_cjk(), 4);
|
||||
/// assert_eq!("│".width_cjk(), 2); // this is correct for cjk
|
||||
/// assert_eq!(tabs.width_cjk(), 18); // " 你 │ 好 │ 世界 "
|
||||
/// ```
|
||||
fn width_cjk(&self) -> usize {
|
||||
let titles_width = self.titles.iter().map(Line::width_cjk).sum::<usize>();
|
||||
let title_count = self.titles.len();
|
||||
let divider_count = title_count.saturating_sub(1);
|
||||
let divider_width = divider_count.saturating_mul(self.divider.width_cjk());
|
||||
let left_padding_width = title_count.saturating_mul(self.padding_left.width_cjk());
|
||||
let right_padding_width = title_count.saturating_mul(self.padding_right.width_cjk());
|
||||
titles_width + divider_width + left_padding_width + right_padding_width
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::{format, vec};
|
||||
@ -697,4 +756,50 @@ mod tests {
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
tabs.render(buffer.area, &mut buffer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_basic() {
|
||||
let tabs = Tabs::new(vec!["A", "BB", "CCC"]);
|
||||
let rendered = " A │ BB │ CCC ";
|
||||
assert_eq!(tabs.width(), rendered.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_no_padding() {
|
||||
let tabs = Tabs::new(vec!["A", "BB", "CCC"]).padding("", "");
|
||||
let rendered = "A│BB│CCC";
|
||||
assert_eq!(tabs.width(), rendered.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_custom_divider_and_padding() {
|
||||
let tabs = Tabs::new(vec!["A", "BB", "CCC"])
|
||||
.divider("--")
|
||||
.padding("X", "YY");
|
||||
let rendered = "XAYY--XBBYY--XCCCYY";
|
||||
assert_eq!(tabs.width(), rendered.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_empty_titles() {
|
||||
let tabs = Tabs::new(Vec::<&str>::new());
|
||||
let rendered = "";
|
||||
assert_eq!(tabs.width(), rendered.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_cjk() {
|
||||
let tabs = Tabs::new(vec!["你", "好", "世界"]);
|
||||
let rendered = " 你 │ 好 │ 世界 ";
|
||||
assert_eq!(tabs.width_cjk(), UnicodeWidthStr::width_cjk(rendered));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_cjk_custom_padding_and_divider() {
|
||||
let tabs = Tabs::new(vec!["你", "好", "世界"])
|
||||
.divider("分")
|
||||
.padding("左", "右");
|
||||
let rendered = "左你右分左好右分左世界右";
|
||||
assert_eq!(tabs.width_cjk(), UnicodeWidthStr::width_cjk(rendered));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user