fix(tabs): Tab widget now supports custom padding (#629)

The Tab widget now contains padding_left and and padding_right
properties. Those values can be set with functions `padding_left()`,
`padding_right()`, and `padding()` whic all accept `Into<Line>`.

Fixes issue https://github.com/ratatui-org/ratatui/issues/502
This commit is contained in:
Rhaskia 2023-12-04 13:38:43 +13:00 committed by GitHub
parent 458fa90362
commit 28ac55bc62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -12,7 +12,9 @@ use crate::{
/// ///
/// Each tab title is stored as a [`Line`] which can be individually styled. The selected tab is set /// Each tab title is stored as a [`Line`] which can be individually styled. The selected tab is set
/// using [`Tabs::select`] and styled using [`Tabs::highlight_style`]. The divider can be customized /// using [`Tabs::select`] and styled using [`Tabs::highlight_style`]. The divider can be customized
/// with [`Tabs::divider`]. /// with [`Tabs::divider`]. Padding can be set with [`Tabs::padding`] or [`Tabs::left_padding`] and [`Tabs::right_padding`].
///
/// The divider defaults to |, and padding defaults to a singular space on each side.
/// ///
/// # Example /// # Example
/// ///
@ -24,7 +26,8 @@ use crate::{
/// .style(Style::default().white()) /// .style(Style::default().white())
/// .highlight_style(Style::default().yellow()) /// .highlight_style(Style::default().yellow())
/// .select(2) /// .select(2)
/// .divider(symbols::DOT); /// .divider(symbols::DOT)
/// .padding("->", "<-");
/// ``` /// ```
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Tabs<'a> { pub struct Tabs<'a> {
@ -40,6 +43,10 @@ pub struct Tabs<'a> {
highlight_style: Style, highlight_style: Style,
/// Tab divider /// Tab divider
divider: Span<'a>, divider: Span<'a>,
/// Tab Left Padding
padding_left: Line<'a>,
/// Tab Right Padding
padding_right: Line<'a>,
} }
impl<'a> Tabs<'a> { impl<'a> Tabs<'a> {
@ -72,6 +79,8 @@ impl<'a> Tabs<'a> {
style: Style::default(), style: Style::default(),
highlight_style: Style::default(), highlight_style: Style::default(),
divider: Span::raw(symbols::line::VERTICAL), divider: Span::raw(symbols::line::VERTICAL),
padding_left: Line::from(" "),
padding_right: Line::from(" "),
} }
} }
@ -83,7 +92,7 @@ impl<'a> Tabs<'a> {
/// Sets the selected tab. /// Sets the selected tab.
/// ///
/// The first tab has index 0 (this is also the default index). /// The first tab has index 0 (this is also the default index).
/// The selected tab can have a different style with [`Tabs::highlight_style`]. /// The selected tab can have a different style with [`Tabs::highlight_style`].
pub fn select(mut self, selected: usize) -> Tabs<'a> { pub fn select(mut self, selected: usize) -> Tabs<'a> {
self.selected = selected; self.selected = selected;
@ -121,7 +130,7 @@ impl<'a> Tabs<'a> {
/// ``` /// ```
/// Use dash (`-`) as separator. /// Use dash (`-`) as separator.
/// ``` /// ```
/// # use ratatui::{prelude::*, widgets::Tabs, symbols}; /// # use ratatui::{prelude::*, widgets::Tabs};
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).divider("-"); /// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).divider("-");
/// ``` /// ```
pub fn divider<T>(mut self, divider: T) -> Tabs<'a> pub fn divider<T>(mut self, divider: T) -> Tabs<'a>
@ -131,6 +140,70 @@ impl<'a> Tabs<'a> {
self.divider = divider.into(); self.divider = divider.into();
self self
} }
/// Sets the padding between tabs.
///
/// Both default to space.
///
/// # Examples
///
/// A space on either side of the tabs.
/// ```
/// # use ratatui::{prelude::*, widgets::Tabs};
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding(" ", " ");
/// ```
/// Nothing on either side of the tabs.
/// ```
/// # use ratatui::{prelude::*, widgets::Tabs};
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding("", "");
/// ```
pub fn padding<T, U>(mut self, left: T, right: U) -> Tabs<'a>
where
T: Into<Line<'a>>,
U: Into<Line<'a>>,
{
self.padding_left = left.into();
self.padding_right = right.into();
self
}
/// Sets the left side padding between tabs.
///
/// Defaults to a space.
///
/// # Example
///
/// An arrow on the left of tabs.
/// ```
/// # use ratatui::{prelude::*, widgets::Tabs};
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding_left("->");
/// ```
pub fn padding_left<T>(mut self, padding: T) -> Tabs<'a>
where
T: Into<Line<'a>>,
{
self.padding_left = padding.into();
self
}
/// Sets the right side padding between tabs.
///
/// Defaults to a space.
///
/// # Example
///
/// An arrow on the right of tabs.
/// ```
/// # use ratatui::{prelude::*, widgets::Tabs};
/// let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding_right("<-");
/// ```
pub fn padding_right<T>(mut self, padding: T) -> Tabs<'a>
where
T: Into<Line<'a>>,
{
self.padding_left = padding.into();
self
}
} }
impl<'a> Styled for Tabs<'a> { impl<'a> Styled for Tabs<'a> {
@ -165,11 +238,21 @@ impl<'a> Widget for Tabs<'a> {
let titles_length = self.titles.len(); let titles_length = self.titles.len();
for (i, title) in self.titles.into_iter().enumerate() { for (i, title) in self.titles.into_iter().enumerate() {
let last_title = titles_length - 1 == i; let last_title = titles_length - 1 == i;
x = x.saturating_add(1); let remaining_width = tabs_area.right().saturating_sub(x);
if remaining_width == 0 {
break;
}
// Left Padding
let pos = buf.set_line(x, tabs_area.top(), &self.padding_left, remaining_width);
x = pos.0;
let remaining_width = tabs_area.right().saturating_sub(x); let remaining_width = tabs_area.right().saturating_sub(x);
if remaining_width == 0 { if remaining_width == 0 {
break; break;
} }
// Title
let pos = buf.set_line(x, tabs_area.top(), &title, remaining_width); let pos = buf.set_line(x, tabs_area.top(), &title, remaining_width);
if i == self.selected { if i == self.selected {
buf.set_style( buf.set_style(
@ -182,11 +265,20 @@ impl<'a> Widget for Tabs<'a> {
self.highlight_style, self.highlight_style,
); );
} }
x = pos.0.saturating_add(1); x = pos.0;
let remaining_width = tabs_area.right().saturating_sub(x);
if remaining_width == 0 {
break;
}
// Right Padding
let pos = buf.set_line(x, tabs_area.top(), &self.padding_right, remaining_width);
x = pos.0;
let remaining_width = tabs_area.right().saturating_sub(x); let remaining_width = tabs_area.right().saturating_sub(x);
if remaining_width == 0 || last_title { if remaining_width == 0 || last_title {
break; break;
} }
let pos = buf.set_span(x, tabs_area.top(), &self.divider, remaining_width); let pos = buf.set_span(x, tabs_area.top(), &self.divider, remaining_width);
x = pos.0; x = pos.0;
} }
@ -216,6 +308,8 @@ mod tests {
style: Style::default(), style: Style::default(),
highlight_style: Style::default(), highlight_style: Style::default(),
divider: Span::raw(symbols::line::VERTICAL), divider: Span::raw(symbols::line::VERTICAL),
padding_right: Line::from(" "),
padding_left: Line::from(" "),
} }
); );
} }
@ -235,6 +329,24 @@ mod tests {
); );
} }
#[test]
fn render_no_padding() {
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).padding("", "");
assert_buffer_eq!(
render(tabs, Rect::new(0, 0, 30, 1)),
Buffer::with_lines(vec!["Tab1│Tab2│Tab3│Tab4 "])
);
}
#[test]
fn render_more_padding() {
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]).padding(" ", " ");
assert_buffer_eq!(
render(tabs, Rect::new(0, 0, 30, 1)),
Buffer::with_lines(vec![" Tab1 │ Tab2 │ Tab3 │"])
);
}
#[test] #[test]
fn render_with_block() { fn render_with_block() {
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"]) let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"])