Skip to main content

altui_core/widgets/
tabs.rs

1use crate::{
2    buffer::Buffer,
3    layout::Rect,
4    style::Style,
5    symbols,
6    text::{Span, Spans},
7    widgets::{Block, Widget},
8};
9
10/// A widget to display available tabs in a multiple panels context.
11///
12/// # Examples
13///
14/// ```
15/// # use altui_core::widgets::{Block, Borders, Tabs};
16/// # use altui_core::style::{Style, Color};
17/// # use altui_core::text::{Spans};
18/// # use altui_core::symbols::{DOT};
19/// let titles = ["Tab1", "Tab2", "Tab3", "Tab4"].iter().cloned().map(Spans::from).collect();
20/// let mut tabs = Tabs::new(titles);
21/// tabs.block(Block::default().title("Tabs").borders(Borders::ALL));
22/// tabs.style(Style::default().fg(Color::White));
23/// tabs.highlight_style(Style::default().fg(Color::Yellow));
24/// tabs.divider(DOT);
25/// ```
26#[derive(Debug, Clone)]
27pub struct Tabs<'a> {
28    /// A block to wrap this widget in if necessary
29    block: Option<Block<'a>>,
30    /// One title for each tab
31    titles: Vec<Spans<'a>>,
32    /// The index of the selected tabs
33    selected: usize,
34    /// The style used to draw the text
35    style: Style,
36    /// Style to apply to the selected item
37    highlight_style: Style,
38    /// Tab divider
39    divider: Span<'a>,
40}
41
42impl<'a> Tabs<'a> {
43    pub fn new(titles: Vec<Spans<'a>>) -> Tabs<'a> {
44        Tabs {
45            block: None,
46            titles,
47            selected: 0,
48            style: Default::default(),
49            highlight_style: Default::default(),
50            divider: Span::raw(symbols::line::VERTICAL),
51        }
52    }
53
54    pub fn block(&mut self, block: Block<'a>) {
55        self.block = Some(block);
56    }
57
58    pub fn select(&mut self, selected: usize) {
59        self.selected = selected;
60    }
61
62    pub fn style(&mut self, style: Style) {
63        self.style = style;
64    }
65
66    pub fn highlight_style(&mut self, style: Style) {
67        self.highlight_style = style;
68    }
69
70    pub fn divider<T>(&mut self, divider: T)
71    where
72        T: Into<Span<'a>>,
73    {
74        self.divider = divider.into();
75    }
76}
77
78impl<'a> Widget for Tabs<'a> {
79    fn render(&mut self, area: Rect, buf: &mut Buffer) {
80        buf.set_style(area, self.style);
81        let tabs_area = match self.block.as_mut() {
82            Some(b) => {
83                let inner_area = b.inner(area);
84                b.render(area, buf);
85                inner_area
86            }
87            None => area,
88        };
89
90        if tabs_area.height < 1 {
91            return;
92        }
93
94        let mut x = tabs_area.left();
95        let titles_length = self.titles.len();
96        for (i, title) in self.titles.clone().into_iter().enumerate() {
97            let last_title = titles_length - 1 == i;
98            x = x.saturating_add(1);
99            let remaining_width = tabs_area.right().saturating_sub(x);
100            if remaining_width == 0 {
101                break;
102            }
103            let pos = buf.set_spans(x, tabs_area.top(), &title, remaining_width);
104            if i == self.selected {
105                buf.set_style(
106                    Rect {
107                        x,
108                        y: tabs_area.top(),
109                        width: pos.0.saturating_sub(x),
110                        height: 1,
111                    },
112                    self.highlight_style,
113                );
114            }
115            x = pos.0.saturating_add(1);
116            let remaining_width = tabs_area.right().saturating_sub(x);
117            if remaining_width == 0 || last_title {
118                break;
119            }
120            let pos = buf.set_span(x, tabs_area.top(), &self.divider, remaining_width);
121            x = pos.0;
122        }
123    }
124}