diff --git a/ratatui-widgets/examples/tabs.rs b/ratatui-widgets/examples/tabs.rs new file mode 100644 index 00000000..4009594f --- /dev/null +++ b/ratatui-widgets/examples/tabs.rs @@ -0,0 +1,92 @@ +//! # [Ratatui] `Tabs` example +//! +//! The latest version of this example is available in the [widget 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 +//! [widget examples]: https://github.com/ratatui/ratatui/blob/main/ratatui-widgets/examples +//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md + +use color_eyre::Result; +use ratatui::{ + crossterm::event::{self, Event, KeyCode}, + layout::{Alignment, Constraint, Layout, Offset, Rect}, + style::{Color, Style, Stylize}, + symbols, + text::{Line, Span}, + widgets::{Block, Paragraph, Tabs}, + DefaultTerminal, Frame, +}; + +fn main() -> Result<()> { + color_eyre::install()?; + let terminal = ratatui::init(); + let result = run(terminal); + ratatui::restore(); + result +} + +/// Run the application. +fn run(mut terminal: DefaultTerminal) -> Result<()> { + let mut selected_tab = 0; + loop { + terminal.draw(|frame| draw(frame, selected_tab))?; + if let Event::Key(key) = event::read()? { + match key.code { + KeyCode::Char('q') => break Ok(()), + KeyCode::Right | KeyCode::Char('l') | KeyCode::Tab => { + selected_tab = (selected_tab + 1) % 3; + } + KeyCode::Left | KeyCode::Char('h') => selected_tab = (selected_tab + 2) % 3, + _ => {} + } + } + } +} + +/// Draw the UI with tabs. +fn draw(frame: &mut Frame, selected_tab: usize) { + let vertical = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).spacing(1); + let [top, main] = vertical.areas(frame.area()); + + let title = Line::from_iter([ + Span::from("Tabs Widget").bold(), + Span::from(" (Press 'q' to quit, arrow keys to navigate tabs)"), + ]); + frame.render_widget(title.centered(), top); + + render_content(frame, main, selected_tab); + render_tabs(frame, main.offset(Offset { x: 1, y: 0 }), selected_tab); +} + +/// Render the tabs. +pub fn render_tabs(frame: &mut Frame, area: Rect, selected_tab: usize) { + let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3"]) + .style(Color::White) + .highlight_style(Style::default().magenta().on_black().bold()) + .select(selected_tab) + .divider(symbols::DOT) + .padding(" ", " "); + frame.render_widget(tabs, area); +} + +/// Render the tab content. +pub fn render_content(frame: &mut Frame, area: Rect, selected_tab: usize) { + let text = match selected_tab { + 0 => "Great terminal interfaces start with a single widget.".into(), + 1 => "In the terminal, we don't just render widgets; we create dreams.".into(), + 2 => "Render boldly, style with purpose.".bold(), + _ => unreachable!(), + }; + let block = Paragraph::new(text) + .alignment(Alignment::Center) + .block(Block::bordered()); + frame.render_widget(block, area); +} diff --git a/ratatui/Cargo.toml b/ratatui/Cargo.toml index d9931a3c..0b258261 100644 --- a/ratatui/Cargo.toml +++ b/ratatui/Cargo.toml @@ -270,11 +270,6 @@ name = "table" required-features = ["crossterm"] doc-scrape-examples = true -[[example]] -name = "tabs" -required-features = ["crossterm"] -doc-scrape-examples = true - [[example]] name = "tracing" required-features = ["crossterm"] diff --git a/ratatui/examples/tabs.rs b/ratatui/examples/tabs.rs deleted file mode 100644 index 96768c6b..00000000 --- a/ratatui/examples/tabs.rs +++ /dev/null @@ -1,216 +0,0 @@ -//! # [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, - } - } -}