ttlog-view 0.1.21

TTLog viewer and analysis tool
use crossterm::event::{KeyCode, KeyEvent, MouseEvent};

use crate::widget::Widget;
use ratatui::{
  layout::Rect,
  style::{Color, Modifier, Style},
  text::Line,
  widgets::{Block, BorderType, Borders, Tabs as T},
  Frame,
};

#[derive(Debug, Clone)]
pub enum ViewMode {
  Overview,
  Logs,
  Metrics,
  Alerts,
  Performance,
  Network,
}

impl ViewMode {
  fn as_str(&self) -> &str {
    match self {
      ViewMode::Overview => "Overview",
      ViewMode::Logs => "Logs",
      ViewMode::Metrics => "Metrics",
      ViewMode::Alerts => "Alerts",
      ViewMode::Performance => "Performance",
      ViewMode::Network => "Network",
    }
  }
}

pub struct ListWidget {
  pub id: u8,
  pub title: &'static str,
  pub items: [ViewMode; 2],
  pub selected: usize,
  pub area: Option<Rect>,
  pub focused: bool,
}

impl ListWidget {
  pub fn new() -> Self {
    Self {
      id: 0,
      title: "~ Menu ~",
      items: [ViewMode::Overview, ViewMode::Logs],
      selected: 0,
      area: None,
      focused: false,
    }
  }
}

impl Widget for ListWidget {
  fn render(&mut self, f: &mut Frame<'_>, area: Rect) {
    let tab_titles: Vec<Line> = self
      .items
      .iter()
      .map(|t| Line::from(format!(" {} ", t.as_str())))
      .collect();

    // base style for all tabs
    let base_style = Style::default().fg(Color::DarkGray);

    // highlight style (selected tab)
    let highlight_style = if self.focused {
      Style::default()
        .fg(Color::Black)
        .bg(Color::Blue) // blue background when focused
        .add_modifier(Modifier::BOLD)
    } else {
      Style::default()
        .fg(Color::White)
        .bg(Color::DarkGray) // still give it a background when not focused
        .add_modifier(Modifier::BOLD)
    };

    // border style changes with focus
    let block = Block::default()
      .borders(Borders::ALL)
      .title(self.title)
      .border_type(BorderType::Rounded)
      .border_style(if self.focused {
        Style::default().fg(Color::Cyan)
      } else {
        Style::default().fg(Color::White)
      });

    let tabs = T::new(tab_titles)
      .block(block)
      .select(self.selected)
      .style(base_style)
      .highlight_style(highlight_style); // applies to the *whole tab*, not just text

    f.render_widget(tabs, area);
  }

  fn on_key(&mut self, key: KeyEvent) {
    if !self.focused {
      return;
    }
    match key.code {
      KeyCode::Char('l') => {
        self.selected = (self.selected + 1) % self.items.len();
      },
      KeyCode::Char('h') => {
        self.selected = (self.selected + self.items.len() - 1) % self.items.len();
      },
      _ => {},
    }
  }

  fn on_mouse(&mut self, me: MouseEvent) {
    use crossterm::event::MouseEventKind;

    if let Some(area) = self.area {
      if let MouseEventKind::Down(_) = me.kind {
        // Tabs are drawn on one row (area.top())
        if me.row == area.top() {
          let mut x = area.left() + 1; // +1 for left border
          for (i, item) in self.items.iter().enumerate() {
            let tab_width = item.as_str().len() as u16 + 2; // <-- FIX HERE

            if me.column >= x && me.column < x + tab_width {
              self.selected = i;
              break;
            }

            x += tab_width;
          }
        }
      }
    }
  }
}