lv-tui 0.4.0

A reactive TUI framework for Rust
Documentation
# lv-tui

A reactive TUI framework for Rust. Component tree, reactive state, CSS-like styling, event bubbling, focus management — everything you need to build complex terminal applications.

```rust
use lv_tui::prelude::*;
use lv_tui::Component;

#[derive(Component)]
struct Counter {
    #[reactive(paint, copy)]
    count: i32,
}

impl Counter {
    fn new() -> Self { Self { count: 0 } }
}

#[lv_tui::event_handlers]
impl Counter {
    fn render(&self, cx: &mut RenderCx) {
        cx.line(format!("count: {}", self.get_count()));
        cx.line("press + to increment, q to quit");
    }

    fn on_key_plus(&mut self, cx: &mut EventCx) {
        self.set_count(self.get_count() + 1, cx);
    }

    fn on_key_q(&mut self, cx: &mut EventCx) {
        cx.quit();
    }
}

fn main() -> lv_tui::Result<()> {
    App::new(Counter::new()).run()
}
```

## Features

- **Component tree** — compose UIs with Column, Row, Stack, Block, Scroll, Overlay
- **Reactive state**`#[reactive(paint)]` auto-triggers repaint on change
- **Declarative events**`#[event_handlers]` macro with `on_focus`, `on_blur`, `on_tick`, `on_key_*` naming convention
- **Event bubbling** — Capture → Target → Bubble with `stop_propagation()`
- **Focus management** — Tab/Shift+Tab navigation with Focus/Blur events
- **CSS-like stylesheets** — type/class/id selectors with pseudo-classes (`:focus`, `:hover`, `:disabled`, `:focus-within`) and style inheritance
- **24-bit color**`Color::Rgb(r,g,b)`, `Color::Indexed(i)`, `Color::hex("#ff8800")`, plus `"text".rgb(255,0,0)` via Stylize
- **Unicode** — CJK/Emoji wide characters, text wrap, truncation, alignment
- **Timer API**`cx.set_timer(ms)`, `cx.set_interval(ms)`, `cx.cancel_timer(id)` for one-shot and periodic dispatch
- **Cancellable workers**`cx.spawn_worker()` returns `WorkerId` for tracking and cancellation
- **Headless testing**`Pilot` driver with event injection, buffer inspection, `press()`, `run_until()`
- **Debug view** — press `d` to visualize component borders and labels

## Widgets

| Widget | Description |
|--------|-------------|
| `Label` | Text display with styling |
| `Input` | Single-line text input with cursor |
| `TextArea` | Multi-line editor with undo/redo, line numbers, word navigation |
| `Column` | Vertical layout container |
| `Row` | Horizontal layout container |
| `Stack` | Layered/z-order container |
| `Block` | Border + padding wrapper with optional title |
| `Scroll` | Scrollable content container |
| `Overlay` | Modal dialog with background dimming |
| `Table` | Data table with headers, row/cell selection, fixed rows, sorting |
| `Tabs` | Tabbed container with keyboard navigation |
| `Select` | Dropdown with keyboard navigation |
| `Checkbox` | Toggleable boolean with label |
| `RadioGroup` | Mutually exclusive radio selection |
| `ProgressBar` | Unicode 8-segment progress bar |
| `Dialog` | Border + key bindings (Esc/Enter) wrapper |
| `SplitPane` | Resizable split panels (Ctrl+arrows) |
| `VirtualList` | Virtual-scrolling list for large datasets |
| `Spinner` | Animated loading indicator |
| `DiffView` | Unified diff display for text comparisons |
| `Tree` | Hierarchical tree with expand/collapse, guide lines |
| `MarkdownView` | Markdown renderer (in `lv-tui-markdown` crate) |

## Text System

All widgets accept `impl Into<Text>`, giving you three levels of control:

```rust
use lv_tui::prelude::*;

// Simple: plain text
Label::new("hello")

// Styled: use Stylize trait — named colors, 24-bit, or hex
Label::new(Line::from(vec![
    "Error: ".red().bold(),
    Span::new("not found"),
]))
Label::new(Line::from("Success".rgb(0, 255, 0)))

// Multi-line: build Text from Lines
Label::new(Text::from(vec![
    Line::from(Span::new("Title").bold()),
    Line::from(Span::new("Body")),
]))
```

## Declarative Events

Replace manual `fn event()` with `#[event_handlers]`:

```rust
#[lv_tui::event_handlers]
impl MyWidget {
    fn render(&self, cx: &mut RenderCx) { ... }

    fn on_focus(&mut self, cx: &mut EventCx) { ... }
    fn on_blur(&mut self, cx: &mut EventCx) { ... }
    fn on_key_q(&mut self, cx: &mut EventCx) { cx.quit(); }
    fn on_key_tab(&mut self, cx: &mut EventCx) { ... }
}
```

Handlers follow naming convention: `on_focus`, `on_blur`, `on_tick`, `on_key_<char>` (e.g. `on_key_q`), `on_key_<name>` (e.g. `on_key_tab`, `on_key_enter`, `on_key_space`).

## 24-bit Color

```rust
use lv_tui::prelude::*;

// Construct colors
let c = Color::Rgb(255, 128, 0);
let c = Color::hex("#ff6600").unwrap();
let c = Color::Indexed(196);  // 256-color palette

// Stylize chainable methods
let span = "hello".rgb(255, 0, 0).bold();
let span = "world".hex("#00ff00").underline();
let span = "text".on_rgb(30, 30, 30).white();
```

## CSS Pseudo-classes

```css
Button:focus { fg: white; bg: blue; }
Button:hover { bg: gray; }
Input:disabled { fg: gray; }
```

```rust
use lv_tui::style_parser::{StyleSheet, WidgetState};

let sheet = StyleSheet::parse("Button:focus { fg: blue; }").unwrap();
let state = WidgetState { focused: true, ..Default::default() };
let style = sheet.resolve("Button", None, None, &state);
```

## Timer API

```rust
fn mount(&mut self, cx: &mut EventCx) {
    self.spinner_timer = Some(cx.set_interval(80));  // every 80ms
    self.toast_timer = Some(cx.set_timer(3000));     // one-shot 3s
}

fn event(&mut self, event: &Event, cx: &mut EventCx) {
    if let Event::Timer(id) = event {
        if Some(*id) == self.spinner_timer { self.advance_frame(cx); }
        if Some(*id) == self.toast_timer { self.dismiss_toast(cx); }
    }
}
```

## Testing

```rust
use lv_tui::Pilot;

let mut pilot = Pilot::new(MyWidget::new(), 80, 24);
pilot.focus_first();
pilot.press(Key::Char(' ')).unwrap();   // inject key
pilot.press(Key::Tab).unwrap();

// Assert rendered output
assert!(pilot.frame().cells.iter().any(|c| c.symbol == "✓"));

// Run until condition
let done = pilot.run_until(100, |buf| {
    buf.cells.iter().any(|c| c.symbol == "X")
}).unwrap();
```

## Getting Started

```toml
[dependencies]
lv-tui = "0.3"
```

```bash
cargo run --example counter
cargo run --example demo_app
```

## License

MIT