use std::io::{self}; use color_eyre::Result; use ratatui::{prelude::*, style::palette::tailwind, symbols::border::*, widgets::*}; use strum::{Display, EnumIter, IntoEnumIterator}; use tui_framework_experiment::events::*; use crate::tabs::*; #[derive(Debug)] pub struct App { state: RunningState, selected_tab_index: usize, tabs: Vec, } #[derive(Debug, Default, PartialEq, Eq)] enum RunningState { #[default] Running, Quit, } #[derive(Debug, Display, EnumIter)] enum Tab { Buttons(ButtonsTab), Stack(StackTab), ToggleSwitch(ToggleSwitchTab), } impl Default for App { fn default() -> Self { Self::new() } } impl App { pub fn new() -> Self { Self { state: RunningState::Running, selected_tab_index: 0, tabs: Tab::iter().collect(), } } pub fn run(&mut self, mut terminal: Terminal) -> Result<()> { while self.is_running() { self.draw(&mut terminal)?; self.handle_events()?; } Ok(()) } fn is_running(&self) -> bool { self.state == RunningState::Running } fn draw(&mut self, terminal: &mut Terminal) -> io::Result<()> { terminal.draw(|frame| frame.render_widget(self, frame.area()))?; Ok(()) } fn handle_events(&mut self) -> Result<()> { match Event::try_from(crossterm::event::read()?) { Ok(event) => self.handle_event(event), Err(_) => { // ignore for now. Perhaps change the try_from approach to a method that returns // Option instead of Result } } Ok(()) } fn quit(&mut self) { self.state = RunningState::Quit; } } impl EventHandler for App { fn handle_key(&mut self, key_pressed_event: KeyPressedEvent) { use Key::*; match key_pressed_event.key { Tab => self.next_tab(), BackTab => self.prev_tab(), Char('q') | Esc => self.quit(), _ => { self.selected_tab_mut().handle_key(key_pressed_event); } } } fn handle_mouse(&mut self, event: MouseEvent) { self.selected_tab_mut().handle_mouse(event); } } impl App { fn selected_tab_mut(&mut self) -> &mut Tab { self.tabs.get_mut(self.selected_tab_index).unwrap() } pub fn next_tab(&mut self) { let tab_count = self.tabs.len(); self.selected_tab_index = (self.selected_tab_index + 1) % tab_count; } pub fn prev_tab(&mut self) { let tab_count = self.tabs.len(); self.selected_tab_index = (self.selected_tab_index + tab_count - 1) % tab_count; } } impl Widget for &mut App { fn render(self, area: Rect, buf: &mut Buffer) { use Constraint::*; let layout = Layout::vertical([Length(1), Fill(1), Length(1)]); let [header, body, footer] = layout.areas(area); let layout = Layout::horizontal([Fill(1), Length(15)]); let [tabs, title] = layout.areas(header); self.footer().render(footer, buf); self.title().render(title, buf); self.tabs().render(tabs, buf); self.selected_tab_mut().render(body, buf); } } impl App { fn footer(&self) -> impl Widget { Line::raw("Esc: quit, Tab: next tab, Shift+Tab: prev tab") .style(tailwind::SLATE.c300) .centered() } fn title(&self) -> impl Widget { Line::raw("tui-framework-experiment") .style(tailwind::SLATE.c300) .centered() } fn tabs(&self) -> impl Widget { Tabs::new(self.tabs.iter().map(Tab::title)) .select(self.selected_tab_index) .divider(" ") .padding("", "") .highlight_style(Modifier::BOLD) } } impl Widget for &mut Tab { fn render(self, area: Rect, buf: &mut Buffer) { let block = Block::default() .borders(Borders::ALL) .border_set(PROPORTIONAL_TALL) .border_style(self.color()) .padding(Padding::horizontal(1)); let inner = block.inner(area); block.render(area, buf); match self { Tab::Buttons(buttons) => buttons.render(inner, buf), Tab::Stack(stack) => stack.render(inner, buf), Tab::ToggleSwitch(switches) => switches.render(inner, buf), } } } impl EventHandler for Tab { fn handle_key(&mut self, event: KeyPressedEvent) { match self { Tab::Buttons(buttons) => buttons.handle_key(event), Tab::Stack(stack) => stack.handle_key(event), Tab::ToggleSwitch(switches) => switches.handle_key(event), } } fn handle_mouse(&mut self, event: MouseEvent) { match self { Tab::Buttons(buttons) => buttons.handle_mouse(event), Tab::Stack(_) => {} Tab::ToggleSwitch(switches) => switches.handle_mouse(event), } } } impl Tab { fn title(&self) -> Span<'static> { // use blue, emerald, indigo, red, yellow, ... let bg = match self { Tab::Buttons(_) => tailwind::BLUE.c700, Tab::Stack(_) => tailwind::EMERALD.c700, Tab::ToggleSwitch(_) => tailwind::PURPLE.c700, }; format!(" {self} ").fg(tailwind::SLATE.c200).bg(bg) } fn color(&self) -> Color { match self { Tab::Buttons(_) => tailwind::BLUE.c700, Tab::Stack(_) => tailwind::EMERALD.c700, Tab::ToggleSwitch(_) => tailwind::PURPLE.c700, } } }