dotmax 0.1.7

High-performance terminal braille rendering for images, animations, and graphics
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# Animation Guide

This guide covers the animation system in dotmax, from simple animations to advanced techniques like differential rendering and pre-rendered animation sequences.

## Table of Contents

1. [Overview]#overview
2. [Quick Start]#quick-start
3. [AnimationLoop (High-Level API)]#animationloop-high-level-api
4. [FrameTimer (Frame Rate Control)]#frametimer-frame-rate-control
5. [FrameBuffer (Double Buffering)]#framebuffer-double-buffering
6. [PrerenderedAnimation (Cached Sequences)]#prerenderedanimation-cached-sequences
7. [DifferentialRenderer (Optimized Updates)]#differentialrenderer-optimized-updates
8. [Example Gallery]#example-gallery
9. [Performance Tips]#performance-tips

## Overview

Dotmax provides a comprehensive animation system built on these core components:

| Component | Purpose | When to Use |
|-----------|---------|-------------|
| `AnimationLoop` | High-level animation abstraction | Most applications |
| `FrameTimer` | Consistent frame rate control | Custom animation loops |
| `FrameBuffer` | Double-buffering for flicker-free updates | Direct buffer control |
| `PrerenderedAnimation` | Cached frame sequences | Repeating animations |
| `DifferentialRenderer` | Only render changed cells | Large static backgrounds |

## Quick Start

The fastest way to create an animation:

```rust
use dotmax::animation::AnimationLoop;

fn main() -> Result<(), dotmax::DotmaxError> {
    AnimationLoop::new(80, 24)
        .fps(60)
        .on_frame(|frame, buffer| {
            // Calculate position (bouncing effect)
            let x = (frame as usize * 2) % 160;
            let y = (frame as usize * 3) % 96;
            buffer.set_dot(x, y)?;
            Ok(true) // Continue animation
        })
        .run()
}
```

## AnimationLoop (High-Level API)

`AnimationLoop` is the recommended way to create animations. It handles:

- Terminal setup (raw mode, alternate screen, cursor hiding)
- Double-buffering automatically
- Frame timing for consistent FPS
- Graceful cleanup on exit (Ctrl+C, 'q', or callback returning `false`)

### Basic Usage

```rust
use dotmax::animation::AnimationLoop;

AnimationLoop::new(80, 24)  // Width × Height in terminal cells
    .fps(60)                 // Target 60 frames per second
    .on_frame(|frame, buffer| {
        // `frame` starts at 0, increments each frame
        // `buffer` is a mutable BrailleGrid (back buffer)

        // Draw your animation here
        buffer.set_dot(frame as usize % 160, 48)?;

        // Return true to continue, false to stop
        Ok(frame < 1000)
    })
    .run()?;
```

### Frame Callback

The callback receives:
- `frame: u64` - Frame number (starts at 0)
- `buffer: &mut BrailleGrid` - The back buffer to draw on

Return values:
- `Ok(true)` - Continue to next frame
- `Ok(false)` - Stop animation gracefully
- `Err(...)` - Stop with error

The back buffer is **automatically cleared** before each callback.

### Configuration Options

```rust
AnimationLoop::new(width, height)
    .fps(30)  // FPS range: 1-240 (clamped automatically)
```

## FrameTimer (Frame Rate Control)

For custom animation loops, `FrameTimer` provides frame rate control:

```rust
use dotmax::animation::FrameTimer;

let mut timer = FrameTimer::new(60); // Target 60 FPS

loop {
    // ... render frame ...

    timer.wait_for_next_frame();

    // Check actual performance
    println!("FPS: {:.1}", timer.actual_fps());
    println!("Last frame: {:?}", timer.frame_time());
}
```

### Key Methods

| Method | Description |
|--------|-------------|
| `new(fps)` | Create timer targeting given FPS (1-240) |
| `wait_for_next_frame()` | Sleep until next frame time |
| `actual_fps()` | Rolling average of achieved FPS |
| `frame_time()` | Duration of last frame |
| `target_fps()` | Configured target FPS |
| `target_frame_time()` | Target duration per frame |
| `reset()` | Clear frame history |

### Frame Drop Handling

When a frame takes longer than target duration:
- No sleep occurs (already late)
- Debug log emitted via `tracing`
- Actual FPS reflects the slowdown
- No "catch-up" frames attempted

## FrameBuffer (Double Buffering)

`FrameBuffer` provides explicit double-buffering control:

```rust
use dotmax::animation::FrameBuffer;
use dotmax::TerminalRenderer;

let mut frame_buffer = FrameBuffer::new(80, 24);
let mut renderer = TerminalRenderer::new()?;

loop {
    // Get back buffer and clear it
    let buffer = frame_buffer.get_back_buffer();
    buffer.clear();

    // Draw to back buffer
    buffer.set_dot(x, y)?;

    // Swap front/back (O(1) pointer swap)
    frame_buffer.swap_buffers();

    // Render front buffer to terminal
    frame_buffer.render(&mut renderer)?;
}
```

### Why Double Buffering?

Without double buffering, drawing directly causes **screen tearing** and **flicker** because the display shows partial frames. Double buffering ensures:

1. All drawing happens on invisible back buffer
2. Atomic swap makes complete frame visible
3. No tearing or partial updates

## PrerenderedAnimation (Cached Sequences)

For repeating animations, pre-render frames once and replay:

```rust
use dotmax::animation::PrerenderedAnimation;
use dotmax::BrailleGrid;

// Pre-render 60 frames
let mut frames = Vec::new();
for i in 0..60 {
    let mut grid = BrailleGrid::new(80, 24)?;
    // Draw frame i onto grid
    draw_frame(&mut grid, i)?;
    frames.push(grid);
}

// Create cached animation
let mut anim = PrerenderedAnimation::new(frames);

// Playback loop
loop {
    let frame = anim.get_current_frame();
    renderer.render(frame)?;
    anim.advance();
}
```

### Looping Modes

```rust
// Loop forever (default)
let anim = PrerenderedAnimation::new(frames);

// Play once, stop at last frame
anim.set_looping(false);

// Get frame by index
let frame = anim.get_frame(30)?;

// Reset to first frame
anim.reset();
```

### Memory Considerations

Each `BrailleGrid` uses ~2KB for 80×24. A 60-frame animation uses ~120KB. Consider:
- Smaller grid dimensions
- Fewer frames with interpolation
- Generating frames on-demand for very long sequences

## DifferentialRenderer (Optimized Updates)

When most of the screen is static, only render changed cells:

```rust
use dotmax::animation::DifferentialRenderer;
use dotmax::BrailleGrid;

let mut diff_renderer = DifferentialRenderer::new(80, 24);
let mut current = BrailleGrid::new(80, 24)?;

// Initial full render
diff_renderer.full_render(&current, &mut renderer)?;

loop {
    // Draw new frame
    let new_frame = generate_frame();

    // Only render changed cells
    let changes = diff_renderer.diff_render(&new_frame, &mut renderer)?;
    println!("Changed {} cells", changes);

    current = new_frame;
}
```

### Performance Benefits

- Full render: O(width × height) terminal writes
- Diff render: O(changed_cells) terminal writes
- Typical savings: 90%+ for animations with static backgrounds

### When to Use

Best for:
- UI with static elements and moving highlights
- Games with fixed backgrounds
- Visualizations with updating data regions

Not ideal for:
- Rapidly changing full-screen content
- Particle systems covering the whole screen

## Example Gallery

### 1. Bouncing Ball (Physics Simulation)

```bash
cargo run --example bouncing_ball
```

Demonstrates:
- Gravity and velocity physics
- Bounce damping (energy loss)
- Real-time FPS display
- `AnimationLoop` high-level API

### 2. Loading Spinner (UI Pattern)

```bash
cargo run --example loading_spinner
```

Demonstrates:
- Rotating indicators
- Multiple spinner styles
- Style cycling
- `FrameTimer` for consistent rotation

### 3. Waveform (Visualization)

```bash
cargo run --example waveform
```

Demonstrates:
- Sine wave animation
- Multiple overlapping waves
- Color schemes from Epic 5
- Line drawing primitives

### 4. Fireworks (Particle System)

```bash
cargo run --example fireworks
```

Demonstrates:
- Particle system (position, velocity, lifetime)
- Gravity and drag physics
- Color fading over lifetime
- Random burst patterns

### 5. Analog Clock (Real-Time)

```bash
cargo run --example clock
```

Demonstrates:
- Real-time system clock
- Circle and line drawing
- Colored clock hands
- Low FPS mode (1 FPS efficiency)

## Performance Tips

### 1. Choose Appropriate FPS

```rust
// UI elements: 10-15 FPS sufficient
AnimationLoop::new(80, 24).fps(15)

// Smooth motion: 30-60 FPS
AnimationLoop::new(80, 24).fps(60)

// Real-time clock: 1 FPS
AnimationLoop::new(80, 24).fps(1)
```

### 2. Minimize Drawing Operations

```rust
// Bad: Clear and redraw everything
fn on_frame(frame, buffer) {
    for y in 0..96 {
        for x in 0..160 {
            if should_draw(x, y, frame) {
                buffer.set_dot(x, y)?;
            }
        }
    }
}

// Good: Only draw what changed
fn on_frame(frame, buffer) {
    let (old_x, old_y) = previous_position(frame - 1);
    let (new_x, new_y) = current_position(frame);

    // AnimationLoop clears buffer automatically
    buffer.set_dot(new_x, new_y)?;
}
```

### 3. Use Differential Rendering for Static Content

```rust
// If 90% of screen is static background
let mut diff = DifferentialRenderer::new(80, 24);
diff.diff_render(&new_frame, &mut renderer)?;
```

### 4. Pre-render Repeating Sequences

```rust
// If animation loops every 60 frames
let frames: Vec<BrailleGrid> = (0..60)
    .map(|i| generate_frame(i))
    .collect();
let anim = PrerenderedAnimation::new(frames);
```

### 5. Profile with Tracing

```rust
// Enable tracing to see timing data
tracing_subscriber::fmt()
    .with_max_level(tracing::Level::DEBUG)
    .init();
```

### Expected Performance

| Scenario | Target | Typical |
|----------|--------|---------|
| 60 FPS animation | <16.67ms/frame | ~2-5ms |
| Buffer swap | <1ms | ~2.4ns |
| Full render (80×24) | <10ms | ~3-5ms |
| Diff render (10% change) | <2ms | <1ms |

## Troubleshooting

### Animation Stuttering

1. Check actual FPS with `timer.actual_fps()`
2. Reduce target FPS
3. Simplify drawing operations
4. Use differential rendering

### Terminal Not Cleaning Up

The cleanup is automatic, but if interrupted:
```bash
reset  # Restore terminal
```

### Colors Not Showing

Verify terminal supports 24-bit color:
```rust
use dotmax::detect_color_capability;
let caps = detect_color_capability();
```

## Further Reading

- API Documentation: `cargo doc --open`
- Source Code: `src/animation/`
- More Examples: `examples/animations/`