ratatui/examples/tabs.rs
Emirhan TALA eb1484b6db
docs(examples): update tabs example and tabs.tape (#855)
This PR adds:

for tabs.rs

- general refactoring on code
- subjectively better looking front
- add tailwind colors

for tabs.tape

- change to get better output from the new code

Here is the new output:

![tabs](https://github.com/ratatui-org/ratatui/assets/30180366/0a9371a5-e90d-42ba-aba5-70cbf66afd1f)
2024-01-21 10:23:50 +01:00

167 lines
4.5 KiB
Rust

use std::{error::Error, io};
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{prelude::*, style::palette::tailwind, widgets::*};
use strum::{Display, EnumIter, FromRepr, IntoEnumIterator};
const PALETTES: &[tailwind::Palette] = &[
tailwind::BLUE,
tailwind::EMERALD,
tailwind::INDIGO,
tailwind::RED,
];
const BORDER_TYPES: &[BorderType] = &[
BorderType::Rounded,
BorderType::Plain,
BorderType::Double,
BorderType::Thick,
];
#[derive(Default, Clone, Copy, Display, FromRepr, EnumIter)]
enum SelectedTab {
#[default]
Tab0,
Tab1,
Tab2,
Tab3,
}
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 From<SelectedTab> for Line<'_> {
/// Return enum name as a styled `Line` with two spaces both left and right.
fn from(value: SelectedTab) -> Self {
format!(" {value} ")
.fg(tailwind::SLATE.c200)
.bg(PALETTES[value as usize].c900)
.into()
}
}
struct App {
pub selected_tab: SelectedTab,
}
impl App {
fn new() -> App {
App {
selected_tab: SelectedTab::default(),
}
}
pub fn next(&mut self) {
self.selected_tab = self.selected_tab.next();
}
pub fn previous(&mut self) {
self.selected_tab = self.selected_tab.previous();
}
}
fn main() -> Result<(), Box<dyn Error>> {
// setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// create app and run it
let app = App::new();
let res = run_app(&mut terminal, app);
// restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Err(err) = res {
println!("{err:?}");
}
Ok(())
}
fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
loop {
terminal.draw(|f| ui(f, &app))?;
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
KeyCode::Char('l') | KeyCode::Right => app.next(),
KeyCode::Char('h') | KeyCode::Left => app.previous(),
_ => {}
}
}
}
}
}
fn ui(f: &mut Frame, app: &App) {
let area = f.size();
let vertical = Layout::vertical([Constraint::Length(4), Constraint::Min(3)]);
let [tabs_area, inner_area] = area.split(&vertical);
render_tabs(f, app, tabs_area);
render_inner(f, app, inner_area);
}
fn render_tabs(f: &mut Frame, app: &App, area: Rect) {
let block = Block::new()
.title("Tabs Example".bold())
.title("Use h l or ◄ ► to change tab")
.title_alignment(Alignment::Center)
.padding(Padding::top(1)); // padding to separate tabs from block title.
let selected_tab_index = app.selected_tab as usize;
// Gets tab titles from `SelectedTab::iter()`
let tabs = Tabs::new(SelectedTab::iter())
.block(block)
.highlight_style(
Style::new()
.bg(PALETTES[selected_tab_index].c600)
.underlined(),
)
.select(selected_tab_index)
.padding("", "")
.divider(" | ");
f.render_widget(tabs, area);
}
fn render_inner(f: &mut Frame, app: &App, area: Rect) {
let index = app.selected_tab as usize;
let inner_block = Block::default()
.title(format!("Inner {index}"))
.borders(Borders::ALL)
.border_type(BORDER_TYPES[index])
.border_style(Style::new().fg(PALETTES[index].c600));
f.render_widget(inner_block, area);
}