blaeck 0.4.0

A component-based terminal UI framework for Rust
Documentation
# Animation Timeline System for Blaeck

## Status: Implemented

This document describes the declarative animation timeline system in blaeck.

## Overview

The timeline system replaces manual state-based animation code with a declarative API inspired by video editing software, CSS animations, and game engines.

**Before (manual timing):**
```rust
fn logo_opacity(&self) -> f64 {
    let t = self.boot_time();
    if t < 0.5 { t * 2.0 }
    else if t < 4.0 { 1.0 }
    else if t < 5.0 { 1.0 - (t - 4.0) }
    else { 0.0 }
}
```

**After (declarative timeline):**
```rust
let timeline = Timeline::new()
    .act(Act::new("fade_in").duration(0.5)
        .animate("opacity", 0.0, 1.0, Easing::EaseOutCubic))
    .act(Act::hold("visible", 3.5))
    .act(Act::new("fade_out").duration(1.0)
        .animate("opacity", 1.0, 0.0, Easing::EaseInCubic));

// Query at any time
let opacity: f64 = timeline.at(2.0).get("opacity").unwrap_or(1.0);
```

## Core Types

### Timeline

A sequence of **Acts** that play in order, with optional looping.

```rust
use blaeck::timeline::*;
use blaeck::animation::Easing;

let sequence = Timeline::new()
    .act(Act::new("intro").duration(2.0)
        .animate("opacity", 0.0, 1.0, Easing::EaseOutCubic))
    .act(Act::new("main").duration(5.0))
    .act(Act::new("outro").duration(2.0)
        .animate("opacity", 1.0, 0.0, Easing::EaseInCubic))
    .loop_forever();  // or .loop_from("main")
```

**Loop behaviors:**
- `loop_forever()` - Loop from the beginning
- `loop_from("act_name")` - Play intro once, then loop from specified act

### Act

A named time segment containing animated properties.

```rust
Act::new("panels_enter")
    .duration(2.0)
    // Simple from→to animation
    .animate("opacity", 0.0, 1.0, Easing::EaseOutCubic)
    // Spring physics
    .spring("position", 0.0, 100.0, Spring::preset_bouncy())
    // Staggered animation for multiple items
    .stagger("panel_alpha", 5, 0.0, 1.0, Easing::EaseOutCubic)
    // Callbacks
    .on_enter(|| println!("Act started"))
    .on_exit(|| println!("Act ended"))
```

**Convenience constructors:**
- `Act::new(name)` - Standard act
- `Act::hold(name, duration)` - No animations, just wait
- `Act::transition(name, duration)` - Semantic alias for transitions

### Track

Keyframe-based animation for a single property. Created automatically via `.animate()` or manually for complex sequences:

```rust
let track = Track::new()
    .keyframe(0.0, 0.0f64, Easing::Linear)    // Start at 0
    .keyframe(0.3, 1.0, Easing::EaseOutCubic)  // Quick rise
    .keyframe(0.7, 0.8, Easing::Linear)        // Slight dip
    .keyframe(1.0, 1.0, Easing::EaseInCubic);  // Return to full

Act::new("complex")
    .duration(2.0)
    .track("custom_prop", track)
```

### Animatable Trait

Values that can be interpolated. Built-in implementations:

```rust
pub trait Animatable: Clone + Send + Sync + 'static {
    fn lerp(a: &Self, b: &Self, t: f64) -> Self;
}

// Implemented for:
// - f32, f64, i32, u8
// - (f32, f32), (f64, f64) - positions
// - (u8, u8, u8) - RGB
// - (u8, u8, u8, u8) - RGBA
```

## Advanced Features

### Spring Physics

Natural, physics-based motion:

```rust
Act::new("bounce")
    .duration(1.5)
    .spring("y_position", 0.0, 100.0, Spring::preset_bouncy())
```

**Spring presets:**
- `Spring::preset_gentle()` - Smooth, minimal overshoot
- `Spring::preset_bouncy()` - Playful with visible overshoot
- `Spring::preset_stiff()` - Quick, snappy motion
- `Spring::preset_slow()` - Heavy, deliberate movement

**Custom springs:**
```rust
Spring::new(stiffness, damping)  // mass defaults to 1.0
Spring::with_mass(stiffness, damping, mass)
```

### Stagger Animations

Animate multiple items with cascading delays:

```rust
Act::new("list_enter")
    .duration(1.0)
    .stagger("item_opacity", 5, 0.0, 1.0, Easing::EaseOutCubic)
    .stagger_config("item_opacity", |cfg| cfg
        .delay(0.1)  // 10% of duration between items
        .order(StaggerOrder::Forward))
```

**Stagger orders:**
- `Forward` - First to last (0, 1, 2, ...)
- `Reverse` - Last to first
- `CenterOut` - Center items first, edges last
- `EdgesIn` - Edge items first, center last
- `Random` - Deterministic pseudo-random

**Retrieving stagger values:**
```rust
let item_3_opacity: f64 = state.get_stagger("item_opacity", 3).unwrap_or(0.0);
let all_opacities: Vec<f64> = state.get_stagger_all("item_opacity", 5);
```

### Timeline Callbacks

```rust
Timeline::new()
    .act(Act::new("intro")
        .on_enter(|| log("intro started"))
        .on_exit(|| log("intro ended")))
    .on_loop(|count| log(&format!("Loop #{}", count)))
    .on_act_enter(|name| log(&format!("Entered: {}", name)))
    .on_act_exit(|name| log(&format!("Exited: {}", name)))
```

## Usage

### Static Queries

Query timeline state at any point:

```rust
let timeline = Timeline::new()
    .act(Act::new("fade").duration(1.0)
        .animate("opacity", 0.0, 1.0, Easing::Linear));

let state = timeline.at(0.5);  // Halfway through
let opacity: f64 = state.get("opacity").unwrap_or(0.0);
assert_eq!(opacity, 0.5);
```

### Playing Timeline

For real-time animation:

```rust
let mut playing = timeline.start();

// In render loop:
let state = playing.state();
let opacity: f64 = state.get("opacity").unwrap_or(0.0);

// Controls
playing.pause();
playing.play();
playing.toggle_pause();
playing.seek(1.5);       // Jump to 1.5 seconds
playing.restart();
playing.set_speed(2.0);  // 2x playback

// Queries
playing.elapsed();       // Current time in seconds
playing.progress();      // 0.0-1.0 for non-looping
playing.current_act();   // "fade"
playing.act_progress();  // Progress within current act
playing.is_paused();
```

### Reactive Hook

For use with blaeck's reactive system:

```rust
use blaeck::reactive::*;

fn animated_component(cx: Scope) -> Element {
    let timeline = use_timeline(cx, Timeline::new()
        .act(Act::new("pulse").duration(2.0)
            .animate("scale", 1.0, 1.2, Easing::EaseInOutCubic))
        .loop_forever());

    let scale: f64 = timeline.get_or("scale", 1.0);

    element! {
        Box(width: 10.0 * scale as f32) {
            Text(content: "Pulsing!")
        }
    }
}
```

**TimelineHandle methods:**
```rust
// Values
timeline.get::<T>(property)
timeline.get_or::<T>(property, default)
timeline.get_stagger::<T>(property, index)
timeline.get_stagger_all::<T>(property, count)

// State
timeline.elapsed()
timeline.current_act()
timeline.act_progress()
timeline.progress()
timeline.loop_count()

// Controls
timeline.pause()
timeline.play()
timeline.toggle_pause()
timeline.seek(time)
timeline.restart()
timeline.set_speed(multiplier)

// Debug
timeline.debug_info()  // For visualization
timeline.update()      // Fire pending callbacks
```

### Chaining Timelines

```rust
let intro = Timeline::new()
    .act(Act::new("logo").duration(2.0));

let main = Timeline::new()
    .act(Act::new("content").duration(5.0))
    .loop_forever();

let full = intro.then(main);  // Plays intro, then loops main
```

## Debug Visualization

The `TimelineDebugInfo` struct provides data for building debug UIs:

```rust
let debug = timeline.debug_info();

debug.elapsed           // Current time
debug.total_duration    // Full timeline length
debug.current_act_name  // Active act
debug.current_act_index // Active act index
debug.act_progress      // Progress in current act
debug.loop_count        // Times looped
debug.is_paused         // Playback state
debug.speed             // Playback speed
debug.acts              // Vec of (name, start_time, duration)
```

## Examples

**Basic fade sequence:**
```rust
Timeline::new()
    .act(Act::new("in").duration(0.5)
        .animate("opacity", 0.0, 1.0, Easing::EaseOutCubic))
    .act(Act::hold("visible", 3.0))
    .act(Act::new("out").duration(0.5)
        .animate("opacity", 1.0, 0.0, Easing::EaseInCubic))
```

**Staggered list with spring:**
```rust
Timeline::new()
    .act(Act::new("enter").duration(1.5)
        .stagger("y", 10, -50.0, 0.0, Easing::EaseOutCubic)
        .stagger_config("y", |c| c.delay(0.08).order(StaggerOrder::Forward))
        .stagger("opacity", 10, 0.0, 1.0, Easing::EaseOutCubic))
```

**Looping ambient animation:**
```rust
Timeline::new()
    .act(Act::new("breathe_in").duration(2.0)
        .animate("scale", 1.0, 1.05, Easing::EaseInOutCubic))
    .act(Act::new("breathe_out").duration(2.0)
        .animate("scale", 1.05, 1.0, Easing::EaseInOutCubic))
    .loop_forever()
```

## Files

- `blaeck/src/timeline.rs` - Core module
- `blaeck/src/reactive/hooks.rs` - `use_timeline` hook
- `blaeck/examples/timeline_demo.rs` - Basic example
- `blaeck/examples/reactive_timeline.rs` - Reactive example
- `blaeck/examples/stagger_demo.rs` - Stagger example
- `blaeck/examples/timeline_debug.rs` - Debug visualization

## Future Work

- [ ] Timeline serialization (YAML/JSON)
- [ ] Timeline interruption/blending
- [ ] Parallel tracks (multiple properties animating independently)
- [ ] Derive macro for custom Animatable types
- [ ] Timeline editor/visualizer tool