use crate::{
buffer::Buffer,
layout::Rect,
style::{Style, Styled},
symbols,
text::{Line, Span},
widgets::{Block, Widget},
};
#[derive(Debug, Clone)]
pub struct Tabs<'a> {
block: Option<Block<'a>>,
titles: Vec<Line<'a>>,
selected: usize,
style: Style,
highlight_style: Style,
divider: Span<'a>,
}
impl<'a> Tabs<'a> {
pub fn new<T>(titles: Vec<T>) -> Tabs<'a>
where
T: Into<Line<'a>>,
{
Tabs {
block: None,
titles: titles.into_iter().map(Into::into).collect(),
selected: 0,
style: Style::default(),
highlight_style: Style::default(),
divider: Span::raw(symbols::line::VERTICAL),
}
}
pub fn block(mut self, block: Block<'a>) -> Tabs<'a> {
self.block = Some(block);
self
}
pub fn select(mut self, selected: usize) -> Tabs<'a> {
self.selected = selected;
self
}
pub fn style(mut self, style: Style) -> Tabs<'a> {
self.style = style;
self
}
pub fn highlight_style(mut self, style: Style) -> Tabs<'a> {
self.highlight_style = style;
self
}
pub fn divider<T>(mut self, divider: T) -> Tabs<'a>
where
T: Into<Span<'a>>,
{
self.divider = divider.into();
self
}
}
impl<'a> Styled for Tabs<'a> {
type Item = Tabs<'a>;
fn style(&self) -> Style {
self.style
}
fn set_style(self, style: Style) -> Self::Item {
self.style(style)
}
}
impl<'a> Widget for Tabs<'a> {
fn render(mut self, area: Rect, buf: &mut Buffer) {
buf.set_style(area, self.style);
let tabs_area = match self.block.take() {
Some(b) => {
let inner_area = b.inner(area);
b.render(area, buf);
inner_area
}
None => area,
};
if tabs_area.height < 1 {
return;
}
let mut x = tabs_area.left();
let titles_length = self.titles.len();
for (i, title) in self.titles.into_iter().enumerate() {
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;
}
let pos = buf.set_line(x, tabs_area.top(), &title, remaining_width);
if i == self.selected {
buf.set_style(
Rect {
x,
y: tabs_area.top(),
width: pos.0.saturating_sub(x),
height: 1,
},
self.highlight_style,
);
}
x = pos.0.saturating_add(1);
let remaining_width = tabs_area.right().saturating_sub(x);
if remaining_width == 0 || last_title {
break;
}
let pos = buf.set_span(x, tabs_area.top(), &self.divider, remaining_width);
x = pos.0;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::{Color, Modifier, Stylize};
#[test]
fn can_be_stylized() {
assert_eq!(
Tabs::new(vec![""])
.black()
.on_white()
.bold()
.not_italic()
.style,
Style::default()
.fg(Color::Black)
.bg(Color::White)
.add_modifier(Modifier::BOLD)
.remove_modifier(Modifier::ITALIC)
)
}
}