Documentation
# tabitha

An async, event-driven TUI framework built on [ratatui](https://github.com/ratatui-org/ratatui) and [tokio](https://tokio.rs/).

## Overview

Tabitha provides a clean architecture for building terminal user interfaces with minimal CPU usage. It only redraws in response to events, making it ideal for applications that need to be "quiet" and power-efficient.

## Features

- **Event-driven**: No polling – only responds to terminal events and task messages
- **Async tasks**: Background tasks communicate via typed message channels
- **Builder pattern**: Clean, composable application setup
- **Tabs support**: Built-in tab management with enable/disable support
- **Focus management**: Navigate between focusable components
- **Minimal allocations**: Designed for efficiency in hot paths
- **Runtime control**: Toggle mouse capture, navigate tabs, quit via contexts

## Installation

Add tabitha to your `Cargo.toml`:

```toml
[dependencies]
tabitha = "0.0.1"
ratatui = "0.30"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```

## Quick Start

```rust
use tabitha::{AppBuilder, Component, MainUi, Event, AppContext, DrawContext, EventResult};
use ratatui::{Frame, layout::Rect, widgets::Paragraph};

struct MyApp;

impl Component for MyApp {
    fn draw(&self, frame: &mut Frame, area: Rect, ctx: &DrawContext) {
        frame.render_widget(Paragraph::new("Hello, tabitha!"), area);
    }

    fn handle_event(&mut self, event: &Event, ctx: &mut AppContext) -> EventResult {
        if event.is_quit() {
            ctx.quit();
            return EventResult::Handled;
        }
        EventResult::Unhandled
    }
}

impl MainUi for MyApp {}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = AppBuilder::new()
        .main_ui(MyApp)
        .build()?;

    app.run().await?;
    Ok(())
}
```

## Core Concepts

### Components

Components are the building blocks of your TUI application. Implement the `Component` trait to create UI elements:

```rust
use tabitha::{Component, Event, AppContext, DrawContext, EventResult, KeyCode};
use ratatui::{Frame, layout::Rect, widgets::Paragraph};

struct Counter {
    count: u32,
}

impl Component for Counter {
    fn draw(&self, frame: &mut Frame, area: Rect, ctx: &DrawContext) {
        let text = format!("Count: {}", self.count);
        frame.render_widget(Paragraph::new(text), area);
    }

    fn handle_event(&mut self, event: &Event, ctx: &mut AppContext) -> EventResult {
        if event.is_key(KeyCode::Up) {
            self.count = self.count.saturating_add(1);
            EventResult::Handled
        } else if event.is_key(KeyCode::Down) {
            self.count = self.count.saturating_sub(1);
            EventResult::Handled
        } else {
            EventResult::Unhandled
        }
    }
}
```

### Tabs

Register tabs with the application and use the context to draw and navigate them:

```rust
use tabitha::{Tab, AppBuilder, Component, MainUi, DrawContext, AppContext, EventResult, KeyCode};
use ratatui::{Frame, layout::Rect, widgets::Paragraph};

struct HomeTab;

impl Tab for HomeTab {
    fn id(&self) -> &str { "home" }
    fn title(&self) -> &str { "Home" }
    
    fn draw(&self, frame: &mut Frame, area: Rect) {
        frame.render_widget(Paragraph::new("Home content"), area);
    }
}

struct MyApp;

impl Component for MyApp {
    fn draw(&self, frame: &mut Frame, area: Rect, ctx: &DrawContext) {
        // Draw tab bar and content
        ctx.tabs().draw_tabbar(frame, tab_bar_area);
        ctx.tabs().draw_content(frame, content_area);
    }

    fn handle_event(&mut self, event: &Event, ctx: &mut AppContext) -> EventResult {
        // Navigate with Tab key
        if event.is_key(KeyCode::Tab) {
            ctx.tabs().select_next();
            return EventResult::Handled;
        }
        EventResult::Unhandled
    }
}

impl MainUi for MyApp {}

// Build with tabs
let app = AppBuilder::new()
    .main_ui(MyApp)
    .add_tab(HomeTab)
    .add_tab(SettingsTab)
    .build()?;
```

Tabs can be enabled/disabled at runtime:

```rust
// In your event handler
ctx.tabs().set_enabled("settings", false);  // Disable a tab
ctx.tabs().is_enabled("settings");           // Check if enabled
```

### Background Tasks

Create async background tasks that communicate with the UI via typed messages:

```rust
use tabitha::{Task, TaskContext, TaskSender, MainUi};
use std::time::Duration;

#[derive(Debug)]
struct TickMessage(u64);

struct TickerTask {
    interval: Duration,
}

impl Task for TickerTask {
    type Message = TickMessage;

    async fn run(self, sender: TaskSender<Self::Message>, mut ctx: TaskContext) {
        let mut count = 0u64;
        let mut interval = tokio::time::interval(self.interval);

        loop {
            tokio::select! {
                _ = interval.tick() => {
                    count += 1;
                    if sender.send(TickMessage(count)).await.is_err() {
                        break;
                    }
                }
                _ = ctx.cancelled() => {
                    break;
                }
            }
        }
    }
}

// Handle messages in your MainUi
impl MainUi for MyApp {
    fn handle_task_message(
        &mut self,
        task_name: &str,
        message: Box<dyn std::any::Any + Send>,
        ctx: &mut AppContext,
    ) -> bool {
        if task_name == "ticker" {
            if let Some(TickMessage(count)) = message.downcast_ref::<TickMessage>() {
                // Handle the message
                return true; // Request redraw
            }
        }
        false
    }
}

// Register the task
let app = AppBuilder::new()
    .main_ui(MyApp::new())
    .add_task("ticker", TickerTask { interval: Duration::from_secs(1) })
    .build()?;
```

### Focus Management

Components can participate in focus navigation:

```rust
impl Component for MyWidget {
    fn focus_id(&self) -> Option<&str> {
        Some("my_widget")  // Makes this component focusable
    }

    fn is_focusable(&self) -> bool {
        true  // Can be toggled dynamically
    }

    fn on_focus(&mut self) {
        // Called when focus is gained
    }

    fn on_blur(&mut self) {
        // Called when focus is lost
    }
}

// Navigate focus in event handlers
if event.is_key(KeyCode::Tab) {
    ctx.focus().focus_next();
    return EventResult::Handled;
}
```

### Event Handling

Events are handled through the `handle_event` method with an `EventResult` return type:

- `EventResult::Handled` – Event consumed, stop propagation
- `EventResult::Unhandled` – Bubble to parent component
- `EventResult::StopPropagation` – Stop propagation without handling

Convenience methods on `Event`:

```rust
event.is_quit()                                    // Ctrl+C or Ctrl+Q
event.is_key(KeyCode::Enter)                       // Specific key
event.is_key_with_modifiers(KeyCode::Char('s'), KeyModifiers::CONTROL)
event.is_mouse_click()                             // Any mouse click
event.mouse_position()                             // Get (x, y) if mouse event
```

## Configuration

### Mouse Capture

Mouse capture can be configured at build time or toggled at runtime:

```rust
// At build time
let app = AppBuilder::new()
    .main_ui(MyApp::new())
    .mouse_capture(false)  // Disable mouse capture
    .build()?;

// At runtime
ctx.set_mouse_capture(true);
let enabled = ctx.mouse_capture_enabled();
```

### Tick Rate

For applications that need periodic updates:

```rust
let app = AppBuilder::new()
    .main_ui(MyApp::new())
    .tick_rate(Duration::from_millis(250))  // Optional periodic tick
    .build()?;
```

## Examples

Run the examples to see tabitha in action:

```bash
# Counter with background task
cargo run --example counter

# Tab navigation
cargo run --example tabs

# Focus management with tables
cargo run --example focus_tables
```

## Optional Features

- `blocking-tasks` – Enable helpers for spawning blocking operations on a dedicated thread pool

```toml
[dependencies]
tabitha = { version = "0.0.1", features = ["blocking-tasks"] }
```

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                        AppBuilder                           │
│  - main_ui(component)                                       │
│  - add_tab(tab)                                             │
│  - add_task(name, task)                                     │
│  - mouse_capture(bool)                                      │
│  - tick_rate(duration)                                      │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                           App                               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   MainUi    │  │ TabManager  │  │    FocusManager     │  │
│  │ (Component) │  │   (Tabs)    │  │ (Focus navigation)  │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
│                                                             │
│  ┌─────────────────────────────────────────────────────────┐│
│  │                    Event Loop                           ││
│  │  - Terminal events (keyboard, mouse, resize)            ││
│  │  - Task messages (via MessageBus)                       ││
│  │  - Optional tick timer                                  ││
│  └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                    Background Tasks                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                   │
│  │  Task 1  │  │  Task 2  │  │  Task N  │  ...              │
│  │ (async)  │  │ (async)  │  │ (async)  │                   │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘                   │
│       │             │             │                         │
│       └─────────────┼─────────────┘                         │
│                     │ TaskSender<T>                         │
│                     ▼                                       │
│             ┌───────────────┐                               │
│             │  MessageBus   │ ──► MainUi.handle_task_message│
│             └───────────────┘                               │
└─────────────────────────────────────────────────────────────┘
```

## License

Licensed under either of:

- Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT]LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.