oo-ide 0.0.3

∞ is a terminal IDE focused on low distraction, high usability.
Documentation
//! Tab bar widget for the terminal view.
//!
//! This widget is no longer rendered in the terminal view (replaced by the
//! global status bar), but is kept here for reference / possible future use.
#![allow(dead_code)]

use ratatui::{
    buffer::Buffer,
    layout::Rect,
    style::{Color, Style},
    widgets::Widget,
};

// --------------------------------------------------------------------------
// Colours
// --------------------------------------------------------------------------

const ACTIVE_FG: Color = Color::White;
const ACTIVE_BG: Color = Color::Rgb(70, 70, 140);

// Tabs to the left of the active one — slightly lighter gray.
const GRAY_LEFT_FG: Color = Color::Rgb(190, 190, 190);
const GRAY_LEFT_BG: Color = Color::Rgb(52, 52, 52);

// Tabs to the right of the active one — darker gray.
const GRAY_RIGHT_FG: Color = Color::Rgb(140, 140, 140);
const GRAY_RIGHT_BG: Color = Color::Rgb(36, 36, 36);

// The thin separator drawn between inactive tabs.
const SEP_FG: Color = Color::Rgb(80, 80, 80);

// The filler that fills the rest of the row after the last tab.
const FILL_BG: Color = Color::Rgb(28, 28, 28);

// --------------------------------------------------------------------------
// Widget
// --------------------------------------------------------------------------

pub struct TerminalTabBar<'a> {
    /// (title, tab_id) for every tab, in order.
    pub tabs: &'a [(String, u64)],
    /// Index of the currently active tab.
    pub active: usize,
}

impl<'a> Widget for TerminalTabBar<'a> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        if area.height == 0 {
            return;
        }
        let y = area.top();
        let mut x = area.left();
        let right = area.right();

        // Fill the whole row with the filler background first so any unused
        // space at the end has a consistent colour.
        for col in area.left()..right {
            if let Some(cell) = buf.cell_mut((col, y)) {
                cell.set_char(' ');
                cell.set_style(Style::default().bg(FILL_BG));
            }
        }

        for (i, (title, _id)) in self.tabs.iter().enumerate() {
            let is_active = i == self.active;

            let (fg, bg) = if is_active {
                (ACTIVE_FG, ACTIVE_BG)
            } else if i < self.active {
                (GRAY_LEFT_FG, GRAY_LEFT_BG)
            } else {
                (GRAY_RIGHT_FG, GRAY_RIGHT_BG)
            };

            let label = format!(" {} ", title);
            for ch in label.chars() {
                if x >= right {
                    return;
                }
                if let Some(cell) = buf.cell_mut((x, y)) {
                    cell.set_char(ch);
                    cell.set_style(Style::default().fg(fg).bg(bg));
                }
                x += 1;
            }

            // Draw a thin separator after every inactive tab (except the last).
            if !is_active && i + 1 < self.tabs.len() && x < right {
                if let Some(cell) = buf.cell_mut((x, y)) {
                    cell.set_char('');
                    cell.set_style(Style::default().fg(SEP_FG).bg(
                        // Separator background blends with the right-side tab.
                        if i + 1 == self.active {
                            GRAY_LEFT_BG
                        } else {
                            GRAY_RIGHT_BG
                        },
                    ));
                }
                x += 1;
            }
        }
    }
}