Tabs
Demonstrates the Tabs widget.
git clone https://github.com/ratatui/ratatui.git --branch latestcd ratatuicargo run --example=tabs --features=crossterm
//! # [Ratatui] Tabs example//!//! The latest version of this example is available in the [examples] folder in the repository.//!//! Please note that the examples are designed to be run against the `main` branch of the Github//! repository. This means that you may not be able to compile with the latest release version on//! crates.io, or the one that you have installed locally.//!//! See the [examples readme] for more information on finding examples that match the version of the//! library you are using.//!//! [Ratatui]: https://github.com/ratatui/ratatui//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
use color_eyre::Result;use ratatui::{    buffer::Buffer,    crossterm::event::{self, Event, KeyCode, KeyEventKind},    layout::{Constraint, Layout, Rect},    style::{palette::tailwind, Color, Stylize},    symbols,    text::Line,    widgets::{Block, Padding, Paragraph, Tabs, Widget},    DefaultTerminal,};use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
fn main() -> Result<()> {    color_eyre::install()?;    let terminal = ratatui::init();    let app_result = App::default().run(terminal);    ratatui::restore();    app_result}
#[derive(Default)]struct App {    state: AppState,    selected_tab: SelectedTab,}
#[derive(Default, Clone, Copy, PartialEq, Eq)]enum AppState {    #[default]    Running,    Quitting,}
#[derive(Default, Clone, Copy, Display, FromRepr, EnumIter)]enum SelectedTab {    #[default]    #[strum(to_string = "Tab 1")]    Tab1,    #[strum(to_string = "Tab 2")]    Tab2,    #[strum(to_string = "Tab 3")]    Tab3,    #[strum(to_string = "Tab 4")]    Tab4,}
impl App {    fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {        while self.state == AppState::Running {            terminal.draw(|frame| frame.render_widget(&self, frame.area()))?;            self.handle_events()?;        }        Ok(())    }
    fn handle_events(&mut self) -> std::io::Result<()> {        if let Event::Key(key) = event::read()? {            if key.kind == KeyEventKind::Press {                match key.code {                    KeyCode::Char('l') | KeyCode::Right => self.next_tab(),                    KeyCode::Char('h') | KeyCode::Left => self.previous_tab(),                    KeyCode::Char('q') | KeyCode::Esc => self.quit(),                    _ => {}                }            }        }        Ok(())    }
    pub fn next_tab(&mut self) {        self.selected_tab = self.selected_tab.next();    }
    pub fn previous_tab(&mut self) {        self.selected_tab = self.selected_tab.previous();    }
    pub fn quit(&mut self) {        self.state = AppState::Quitting;    }}
impl SelectedTab {    /// Get the previous tab, if there is no previous tab return the current tab.    fn previous(self) -> Self {        let current_index: usize = self as usize;        let previous_index = current_index.saturating_sub(1);        Self::from_repr(previous_index).unwrap_or(self)    }
    /// Get the next tab, if there is no next tab return the current tab.    fn next(self) -> Self {        let current_index = self as usize;        let next_index = current_index.saturating_add(1);        Self::from_repr(next_index).unwrap_or(self)    }}
impl Widget for &App {    fn render(self, area: Rect, buf: &mut Buffer) {        use Constraint::{Length, Min};        let vertical = Layout::vertical([Length(1), Min(0), Length(1)]);        let [header_area, inner_area, footer_area] = vertical.areas(area);
        let horizontal = Layout::horizontal([Min(0), Length(20)]);        let [tabs_area, title_area] = horizontal.areas(header_area);
        render_title(title_area, buf);        self.render_tabs(tabs_area, buf);        self.selected_tab.render(inner_area, buf);        render_footer(footer_area, buf);    }}
impl App {    fn render_tabs(&self, area: Rect, buf: &mut Buffer) {        let titles = SelectedTab::iter().map(SelectedTab::title);        let highlight_style = (Color::default(), self.selected_tab.palette().c700);        let selected_tab_index = self.selected_tab as usize;        Tabs::new(titles)            .highlight_style(highlight_style)            .select(selected_tab_index)            .padding("", "")            .divider(" ")            .render(area, buf);    }}
fn render_title(area: Rect, buf: &mut Buffer) {    "Ratatui Tabs Example".bold().render(area, buf);}
fn render_footer(area: Rect, buf: &mut Buffer) {    Line::raw("◄ ► to change tab | Press q to quit")        .centered()        .render(area, buf);}
impl Widget for SelectedTab {    fn render(self, area: Rect, buf: &mut Buffer) {        // in a real app these might be separate widgets        match self {            Self::Tab1 => self.render_tab0(area, buf),            Self::Tab2 => self.render_tab1(area, buf),            Self::Tab3 => self.render_tab2(area, buf),            Self::Tab4 => self.render_tab3(area, buf),        }    }}
impl SelectedTab {    /// Return tab's name as a styled `Line`    fn title(self) -> Line<'static> {        format!("  {self}  ")            .fg(tailwind::SLATE.c200)            .bg(self.palette().c900)            .into()    }
    fn render_tab0(self, area: Rect, buf: &mut Buffer) {        Paragraph::new("Hello, World!")            .block(self.block())            .render(area, buf);    }
    fn render_tab1(self, area: Rect, buf: &mut Buffer) {        Paragraph::new("Welcome to the Ratatui tabs example!")            .block(self.block())            .render(area, buf);    }
    fn render_tab2(self, area: Rect, buf: &mut Buffer) {        Paragraph::new("Look! I'm different than others!")            .block(self.block())            .render(area, buf);    }
    fn render_tab3(self, area: Rect, buf: &mut Buffer) {        Paragraph::new("I know, these are some basic changes. But I think you got the main idea.")            .block(self.block())            .render(area, buf);    }
    /// A block surrounding the tab's content    fn block(self) -> Block<'static> {        Block::bordered()            .border_set(symbols::border::PROPORTIONAL_TALL)            .padding(Padding::horizontal(1))            .border_style(self.palette().c700)    }
    const fn palette(self) -> tailwind::Palette {        match self {            Self::Tab1 => tailwind::BLUE,            Self::Tab2 => tailwind::EMERALD,            Self::Tab3 => tailwind::INDIGO,            Self::Tab4 => tailwind::RED,        }    }}