waterui-ffi 0.2.1

FFI bindings for the WaterUI cross-platform UI framework
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
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# waterui-ffi

FFI bindings layer that bridges Rust view trees to native platform backends.

## Overview

`waterui-ffi` is the C FFI layer at the heart of WaterUI's cross-platform architecture. It provides a type-safe, efficient bridge between Rust application logic and native UI backends, enabling WaterUI apps to render as true native widgets rather than custom-drawn pixels.

This crate serves three critical roles:

1. **Entry Point Generation**: The `export!()` macro generates the C ABI entry points (`waterui_init`, `waterui_app`) that native backends call to initialize and run Rust applications.

2. **Type Conversion**: Implements `IntoFFI` and `IntoRust` traits to safely convert between Rust types (views, reactive values, colors, fonts) and C-compatible representations that can cross the FFI boundary.

3. **C Header Generation**: Uses `cbindgen` to automatically generate `waterui.h`, which is consumed by Swift (Apple backend) and Kotlin/JNI (Android backend) to understand the FFI contract.

The FFI layer is designed to work in `no_std` environments and minimizes unsafe code through carefully designed abstractions.

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
waterui = "0.2"
waterui-ffi = "0.1"
```

For applications, you typically only need the `export!()` macro - all other FFI details are handled internally by WaterUI.

## Quick Start

Every WaterUI application uses the FFI layer to expose itself to native platforms:

```rust
use waterui::prelude::*;
use waterui::app::App;

// Your application entry point
pub fn app(env: Environment) -> App {
    App::new(main, env)
}

fn main() -> impl View {
    text("Hello, WaterUI!")
}

// This macro generates waterui_init() and waterui_app() FFI entry points
waterui_ffi::export!();
```

The `export!()` macro expands to:

```rust
#[no_mangle]
pub unsafe extern "C" fn waterui_init() -> *mut WuiEnv {
    // Initialize runtime, logging, executors
    let env = waterui::Environment::new();
    env.into_ffi()
}

#[no_mangle]
pub unsafe extern "C" fn waterui_app(env: *mut WuiEnv) -> WuiApp {
    let env = env.into_rust();
    let app = app(env);  // Call your app() function
    app.into_ffi()
}
```

Native backends then call these functions to initialize and retrieve the root view tree.

## Core Concepts

### FFI Conversion Traits

The crate defines two fundamental traits for crossing the FFI boundary:

**`IntoFFI`** - Converts Rust types to FFI-compatible representations:

```rust
pub trait IntoFFI: 'static {
    type FFI: 'static;
    fn into_ffi(self) -> Self::FFI;
}

// Example: Converting a String to a C-compatible byte array
impl IntoFFI for Str {
    type FFI = WuiStr;
    fn into_ffi(self) -> Self::FFI {
        WuiStr(WuiArray::new(self))
    }
}
```

**`IntoRust`** - Safely converts FFI types back to Rust types:

```rust
pub trait IntoRust {
    type Rust;
    unsafe fn into_rust(self) -> Self::Rust;
}

// Example: Converting C byte array back to String
impl IntoRust for WuiStr {
    type Rust = Str;
    unsafe fn into_rust(self) -> Self::Rust {
        let bytes = unsafe { self.0.into_rust() };
        unsafe { Str::from_utf8_unchecked(bytes) }
    }
}
```

### Opaque Types

For types where the internal structure isn't relevant to native code, use the `OpaqueType` trait:

```rust
impl OpaqueType for Environment {}

// Automatically implements:
// - IntoFFI converting to *mut WuiEnv
// - IntoRust converting from *mut WuiEnv
```

This pattern is used for `Environment`, `AnyView`, `Binding<T>`, and `Computed<T>`, which native code treats as opaque pointers.

### Type Identification

The FFI layer uses `WuiTypeId` for O(1) type identification across the FFI boundary:

```rust
#[repr(C)]
pub struct WuiTypeId {
    pub low: u64,
    pub high: u64,
}
```

In normal builds, this wraps Rust's `TypeId`. In hot reload builds (with `waterui_hot_reload_lib` cfg), it uses a 128-bit FNV-1a hash of the type name, ensuring type IDs remain stable across dylib reloads.

Native backends use type IDs to determine which view type they're rendering:

```swift
let viewId = waterui_view_id(view)

if viewId == waterui_text_id() {
    let textConfig = waterui_force_as_text(view)
    return Text(textConfig.content.get())
} else if viewId == waterui_button_id() {
    // ...
}
```

### View Rendering Protocol

Native backends traverse the view tree using these core functions:

1. **`waterui_view_id(view)`** - Get the 128-bit type ID
2. **`waterui_view_body(view, env)`** - Expand composite views into their body
3. **`waterui_force_as_<type>(view)`** - Downcast to specific view type (Button, Text, etc.)
4. **`waterui_view_stretch_axis(view)`** - Query layout behavior

Composite views (user-defined) are recursively expanded via `body()`. Leaf views (Text, Button, Image) are downcast and mapped directly to native widgets.

## Reactive System FFI

WaterUI's reactive primitives (`Binding`, `Computed`) cross the FFI boundary with full functionality:

### Binding (Read/Write Reactive State)

```rust
// Rust side
let counter = Binding::int(42);

// FFI functions generated by ffi_binding! macro:
// - waterui_read_binding_i32(binding) -> i32
// - waterui_set_binding_i32(binding, value)
// - waterui_watch_binding_i32(binding, watcher) -> guard
// - waterui_drop_binding_i32(binding)
```

Native code can read, write, and subscribe to changes:

```swift
let binding: UnsafeMutablePointer<WuiBinding<Int32>>
let value = waterui_read_binding_i32(binding)
waterui_set_binding_i32(binding, value + 1)

// Watch for changes
let guard = waterui_watch_binding_i32(binding) { newValue, metadata in
    print("Counter changed to \(newValue)")
}
```

### Computed (Read-Only Derived State)

```rust
// Rust side
let doubled = counter.map(|n| n * 2);

// FFI functions generated by ffi_computed! macro:
// - waterui_read_computed_i32(computed) -> i32
// - waterui_watch_computed_i32(computed, watcher) -> guard
// - waterui_clone_computed_i32(computed) -> computed
// - waterui_drop_computed_i32(computed)
```

Computed values can also be created from native code using `waterui_new_computed_<type>()`, enabling native-driven reactivity.

## Architecture

### Data Flow

```
┌─────────────────────────────────────────────────────┐
│ Rust Application (waterui)                         │
│ - View tree definition                             │
│ - Reactive state (Binding, Computed)               │
│ - Business logic                                    │
└─────────────────┬───────────────────────────────────┘
                  │ .into_ffi()
┌─────────────────────────────────────────────────────┐
│ FFI Layer (waterui-ffi)                            │
│ - Entry points: waterui_init(), waterui_app()      │
│ - Type conversion: IntoFFI, IntoRust               │
│ - View traversal: waterui_view_id(), _body()       │
│ - Reactive primitives: Binding, Computed FFI       │
└─────────────────┬───────────────────────────────────┘
                  │ C ABI (waterui.h)
┌─────────────────────────────────────────────────────┐
│ Native Backend (Swift/Kotlin)                      │
│ - Apple: UIView/NSView                             │
│ - Android: Android View                            │
│ - Maps Rust views to platform widgets               │
└─────────────────────────────────────────────────────┘
```

### Component FFI Modules

The `components/` directory contains FFI bindings for each UI component category:

- **`layout`** - HStack, VStack, ZStack, ScrollView, Spacer
- **`button`** - Button with styles (plain, bordered, link)
- **`text`** - Text rendering, fonts, styled text
- **`form`** - TextField, SecureField, Toggle, Slider, Picker
- **`navigation`** - NavigationStack, TabView, NavigationLink
- **`media`** - Image, Video, Audio, LivePhoto
- **`list`** - List, ForEach, LazyVStack
- **`table`** - Table with columns and rows
- **`progress`** - ProgressView, ProgressIndicator
- **`gpu_surface`** - High-performance wgpu rendering surface

Each module defines:
1. C-compatible struct representations (e.g., `WuiButton`)
2. `IntoFFI` implementations for converting Rust view configs
3. FFI view macros generating type ID and downcast functions

### Helper Macros

The crate provides several code generation macros to reduce boilerplate:

**`opaque!(Name, RustType, ident)`** - Generate opaque pointer FFI for a type:
```rust
opaque!(WuiEnv, waterui::Environment, env);
// Generates: WuiEnv wrapper, IntoFFI, IntoRust, waterui_drop_env()
```

**`ffi_view!(RustView, FFIStruct, ident)`** - Generate view FFI functions:
```rust
ffi_view!(ButtonConfig, WuiButton, button);
// Generates: waterui_button_id(), waterui_force_as_button()
```

**`ffi_reactive!(Type, FFIType, ident)`** - Generate binding + computed FFI:
```rust
ffi_reactive!(i32, i32, i32);
// Generates: read/write/watch/drop for both Binding<i32> and Computed<i32>
```

**`into_ffi!{}`** - Derive `IntoFFI` for structs and enums:
```rust
into_ffi! {
    ButtonStyle,
    pub enum WuiButtonStyle {
        Plain, Bordered, Link,
    }
}
```

## Examples

### Creating a New FFI View Type

To add FFI support for a new view component:

```rust
// 1. Define the Rust view config (in components/controls/src/rating.rs)
pub struct RatingConfig {
    pub value: Computed<f32>,
    pub max: f32,
    pub color: Color,
}

// 2. Add FFI bindings (in ffi/src/components/controls.rs)
use crate::{IntoFFI, reactive::WuiComputed, color::WuiColor};

into_ffi! {
    RatingConfig,
    pub struct WuiRating {
        value: *mut WuiComputed<f32>,
        max: f32,
        color: *mut WuiColor,
    }
}

ffi_view!(RatingConfig, WuiRating, rating);

// 3. Regenerate waterui.h
// cargo run --bin generate_header --features cbindgen --manifest-path ffi/Cargo.toml

// 4. Implement in Swift (backends/apple/Sources/WaterUI/Views/Rating.swift)
// if viewId == waterui_rating_id() {
//     let config = waterui_force_as_rating(view)
//     return RatingView(config: config)
// }
```

### Implementing Custom Reactive Properties

```rust
// Generate FFI for a custom type in reactive state
use waterui_color::Color;

ffi_reactive!(Color, *mut WuiColor, color);
// Now Binding<Color> and Computed<Color> can cross FFI
```

### Creating Native-Controlled Computed Values

```rust
// Swift side creates a computed that Rust can read
let computed = waterui_new_computed_i32(
    dataPtr,
    { ptr in return getCurrentValue(ptr) },
    { ptr, watcher in return watchValue(ptr, watcher) },
    { ptr in cleanup(ptr) }
)

// Pass to Rust view
let text = waterui_text(computed)
```

## C Header Generation

The crate includes a `generate_header` binary that uses `cbindgen` to produce `waterui.h`:

```bash
cargo run --bin generate_header --features cbindgen --manifest-path ffi/Cargo.toml
```

This generates the C header and automatically copies it to:
- `backends/apple/Sources/CWaterUI/include/waterui.h` (SwiftUI backend)
- `backends/android/runtime/src/main/cpp/waterui.h` (Android backend)

The header is checked into version control, and CI verifies it's always up-to-date with the Rust code.

## Native Backend Render Pipeline

Native backends (Android, Apple, etc.) must follow a specific initialization sequence when rendering WaterUI views.

### Required Sequence

```
┌─────────────────────────────────────────────────────────────────────┐
│ 1. waterui_init()                                                   │
│    - Initializes panic hooks and global executors                   │
│    - Returns an Environment pointer                                 │
│    - MUST be called first before any other waterui_* functions      │
├─────────────────────────────────────────────────────────────────────┤
│ 2. Theme installation (recommended)                                 │
│    - Install appearance: waterui_theme_install_color_scheme()       │
│    - Install colors:     waterui_theme_install_color()              │
│    - Install fonts:      waterui_theme_install_font()               │
│    - Legacy: waterui_env_install_theme() is deprecated              │
├─────────────────────────────────────────────────────────────────────┤
│ 3. waterui_app(env)                                                 │
│    - Creates the application from user's app(env) function          │
│    - Returns WuiApp with windows and environment                    │
│    - MUST be called AFTER waterui_init() and theme installation     │
├─────────────────────────────────────────────────────────────────────┤
│ 4. Render Loop (for each view)                                      │
│    a. waterui_view_id(view) → Get the type ID                       │
│    b. Check if it's a "raw view" (Text, Button, etc.)               │
│       - If raw: waterui_force_as_*(view) → Extract native data      │
│       - If composite: waterui_view_body(view, env) → Get body view  │
│    c. Render the native widget or recurse into body                 │
└─────────────────────────────────────────────────────────────────────┘
```

### Raw Views vs Composite Views

WaterUI distinguishes between two kinds of views:

- **Raw Views**: Leaf components that map directly to native widgets. Examples: `Text`, `Button`, `Color`, `TextField`, `Toggle`, `Slider`, `Stepper`, `Progress`, `Spacer`, `Picker`, `ScrollView`.

- **Composite Views**: User-defined views that have a `body()` method returning other views. When you encounter a view that isn't in the raw view registry, call `waterui_view_body(view, env)` to get its body and continue rendering recursively.

## Features

- **`std`** (default) - Enable standard library support
- **`cbindgen`** - Required for the `generate_header` binary

## API Overview

### Core Entry Points
- `waterui_init()` - Initialize runtime and return Environment
- `waterui_app(env)` - Create application from user's app() function
- `waterui_env_new()` - Create new Environment (alternative to init)
- `waterui_clone_env(env)` - Clone Environment for child contexts

### View Traversal
- `waterui_view_id(view)` - Get type ID as 128-bit value
- `waterui_view_body(view, env)` - Expand composite view to its body
- `waterui_view_stretch_axis(view)` - Query layout stretch behavior
- `waterui_empty_anyview()` - Create empty view
- `waterui_anyview_id()` - Get AnyView type ID

### Type Downcasting
- `waterui_force_as_<type>(view)` - Downcast to specific view type
- `waterui_<type>_id()` - Get type ID for comparison

### Reactive Primitives
- `waterui_read_binding_<type>(binding)` - Read current value
- `waterui_set_binding_<type>(binding, value)` - Update value
- `waterui_watch_binding_<type>(binding, watcher)` - Subscribe to changes
- `waterui_read_computed_<type>(computed)` - Read derived value
- `waterui_watch_computed_<type>(computed, watcher)` - Subscribe to changes
- `waterui_new_computed_<type>(...)` - Create native-controlled computed
- `waterui_drop_binding_<type>(binding)` - Cleanup binding
- `waterui_drop_computed_<type>(computed)` - Cleanup computed

### Memory Management
- `waterui_drop_<type>(ptr)` - Free resource of given type
- `waterui_drop_retain(retain)` - Drop retained value

## Safety Considerations

The FFI layer involves extensive `unsafe` code by necessity:

1. **Pointer Validity**: Callers must ensure pointers from FFI functions remain valid for their usage
2. **Ownership Transfer**: Functions taking `*mut T` typically take ownership and will free the memory
3. **Thread Safety**: `waterui_init()` must be called once on the main thread only
4. **Type Downcasting**: `waterui_force_as_*()` functions assume the view type matches

Native backends are responsible for:
- Properly managing the lifetimes of FFI pointers
- Calling drop functions when resources are no longer needed
- Not using pointers after they've been consumed

## Performance

The FFI layer is designed for zero-overhead abstraction:

- **Type IDs**: O(1) comparison (128-bit integer equality)
- **View Traversal**: Single pointer dereference per level
- **Reactive Updates**: Direct function pointer callbacks, no allocation
- **Arrays**: Zero-copy views into Rust data via vtable-based slicing

The reactive system uses reference counting (`Rc`) on the Rust side and lets native code subscribe via lightweight watcher callbacks.

## Development Workflow

When adding a new view type to WaterUI:

1. Define the Rust view struct in the appropriate component crate
2. Add FFI bindings in `ffi/src/components/<module>.rs`
3. Regenerate the C header: `cargo run --bin generate_header --features cbindgen --manifest-path ffi/Cargo.toml`
4. Implement the native renderer in Swift (`backends/apple`) and Kotlin (`backends/android`)
5. Update tests to verify FFI contract

The workflow ensures Rust, C header, and native backends stay synchronized.

## License

MIT