dampen-iced 0.2.1

Iced backend implementation for Dampen UI framework
Documentation

Dampen Iced

Iced backend implementation for the Dampen declarative UI framework.

Overview

Dampen Iced provides automatic interpretation of parsed Dampen markup into Iced widgets, eliminating manual conversion boilerplate. It handles bindings, events, styles, and layouts automatically through the DampenWidgetBuilder.

Features

  • Automatic Widget Building: Convert XML markup to Iced widgets with one line
  • Binding Support: Evaluate expressions like {count} or {user.name}
  • Event Handling: Connect handlers via on_click, on_input, etc.
  • Style & Layout: Apply padding, spacing, colors, borders automatically
  • Recursive Processing: Handles nested widget trees automatically
  • Verbose Logging: Debug mode for development

Quick Start

Basic Usage

use dampen_core::{parse, HandlerRegistry};
use dampen_iced::{DampenWidgetBuilder, HandlerMessage};
use dampen_macros::UiModel;
use iced::{Element, Task};
use serde::{Deserialize, Serialize};
use std::any::Any;

#[derive(Default, UiModel, Serialize, Deserialize, Clone, Debug)]
struct Model {
    count: i32,
}

type Message = HandlerMessage;

fn increment(model: &mut Model) {
    model.count += 1;
}

fn decrement(model: &mut Model) {
    model.count -= 1;
}

struct AppState {
    model: Model,
    document: dampen_core::DampenDocument,
    handler_registry: HandlerRegistry,
}

impl AppState {
    fn new() -> Self {
        let xml = include_str!("../ui/main.dampen");
        let document = parse(xml).expect("Failed to parse XML");
        
        let handler_registry = HandlerRegistry::new();
        handler_registry.register_simple("increment", |m: &mut dyn Any| {
            let model = m.downcast_mut::<Model>().unwrap();
            increment(model);
        });
        handler_registry.register_simple("decrement", |m: &mut dyn Any| {
            let model = m.downcast_mut::<Model>().unwrap();
            decrement(model);
        });
        
        Self {
            model: Model::default(),
            document,
            handler_registry,
        }
    }
}

fn view(state: &AppState) -> Element<'_, Message> {
    // Single line to build the entire UI!
    DampenWidgetBuilder::new(
        &state.document.root,
        &state.model,
        Some(&state.handler_registry),
    )
    .build()
}

UI File (ui/main.dampen)

<column padding="40" spacing="20">
    <text value="Count: {count}" size="24" />
    <row spacing="10">
        <button label="Increment" on_click="increment" />
        <button label="Decrement" on_click="decrement" />
    </row>
</column>

API Reference

DampenWidgetBuilder

Constructor

// Using HandlerMessage (recommended for most cases)
DampenWidgetBuilder::new(
    node: &WidgetNode,
    model: &dyn UiBindable,
    handler_registry: Option<&HandlerRegistry>,
) -> Self

// From complete document
DampenWidgetBuilder::from_document(
    document: &DampenDocument,
    model: &dyn UiBindable,
    handler_registry: Option<&HandlerRegistry>,
) -> Self

// With custom message factory
DampenWidgetBuilder::new_with_factory<F>(
    node: &WidgetNode,
    model: &dyn UiBindable,
    handler_registry: Option<&HandlerRegistry>,
    message_factory: F,
) -> Self
where F: Fn(&str) -> Message + 'a

Configuration

// Enable verbose logging for debugging
builder.with_verbose(true)

// Add style classes for theme support
builder.with_style_classes(&style_classes_map)

// Access state manager for event handlers
let manager = builder.state_manager();

Execution

// Build the widget tree
let element: Element<'_, Message> = builder.build();

Supported Widgets

Widget XML Tag Key Attributes
Text <text /> value, size, weight, color
Button <button /> label, on_click, enabled
Column <column /> spacing, padding
Row <row /> spacing, padding
Container <container /> width, height, padding
TextInput <text_input /> value, placeholder, on_input
Checkbox <checkbox /> label, checked, on_toggle
Slider <slider /> min, max, value, on_change
PickList <pick_list /> options, selected, on_select
Toggler <toggler /> label, active, on_toggle
Image <image /> src
Scrollable <scrollable /> (wraps children)
Stack <stack /> (layered children)
Space <space /> (flexible spacer)
Rule <rule /> (horizontal/vertical line)

Binding Expressions

<!-- Simple field access -->
<text value="{count}" />

<!-- Nested fields -->
<text value="{user.profile.name}" />

<!-- Method calls -->
<text value="{items.len()}" />

<!-- Binary operations -->
<text value="{count * 2}" />

<!-- Conditionals -->
<text value="{if count > 10 then 'High' else 'Low'}" />

<!-- Interpolated strings -->
<text value="Count: {count}, User: {username}" />

Event Handlers

// Simple handler (no payload)
fn increment(model: &mut Model) {
    model.count += 1;
}

// Handler with value
fn update_text(model: &mut Model, value: String) {
    model.text = value;
}

// Handler with command (async)
fn fetch_data(model: &mut Model) -> Task<Message> {
    Task::perform(
        async { api::fetch().await },
        Message::DataLoaded,
    )
}

Examples

See the examples/ directory for complete working applications:

  • hello-world: Minimal example showing basic usage
  • counter: Interactive counter with event handlers
  • todo-app: Full CRUD application with bindings
  • styling: Theme and style class examples

Architecture

Design Principles

  1. Centralized Interpretation: All widget rendering logic lives in the builder
  2. Type Safety: No runtime type erasure, compile-time verified
  3. Backend Agnostic: Core logic separate from Iced-specific widgets
  4. Zero Duplication: Reuses existing style mapping and evaluation functions

File Structure

crates/dampen-iced/
├── src/
│   ├── lib.rs           # Public API, IcedBackend trait
│   ├── builder.rs       # DampenWidgetBuilder implementation
│   ├── convert.rs       # IR → Iced type conversions
│   ├── state.rs         # Widget state management
│   ├── style_mapping.rs # Style and layout mapping
│   ├── theme_adapter.rs # Theme conversion
│   └── widgets/         # Widget-specific helpers
└── tests/               # Integration tests

Performance

Benchmarks

  • 100 widgets: ~0.027ms (37x faster than 1ms target)
  • 1000 widgets: ~0.284ms (175x faster than 50ms target)
  • Binding evaluation: ~713ns per widget
  • Event connection: ~784ns per widget

Optimization Tips

  1. Reuse builders: Builder instances are single-use, but cheap to create
  2. Minimize bindings: Each binding adds ~700ns overhead
  3. Use style classes: Reuse styles instead of inline attributes
  4. Profile with verbose: Use with_verbose(true) to identify bottlenecks

Error Handling

Verbose Mode

let widget = DampenWidgetBuilder::new(&node, &model, Some(®istry))
    .with_verbose(true)  // Enable debug logging
    .build();

Common Errors

  1. Missing handler: Logs warning if verbose enabled, event ignored
  2. Binding error: Returns empty string, logs error if verbose
  3. Invalid attribute: Uses default value, logs warning if verbose
  4. Unsupported widget: Returns empty placeholder, logs error

Integration

With CLI

# Development mode
dampen dev --ui ui --file main.dampen --verbose

# Validate XML
dampen check --ui ui

# Inspect IR
dampen inspect --file ui/main.dampen

Best Practices

1. Model Design

#[derive(UiModel, Serialize, Deserialize, Clone)]
struct Model {
    // Simple fields work best
    count: i32,
    name: String,
    
    // Vec for lists
    items: Vec<String>,
    
    // Option for optional state
    selected: Option<usize>,
}

2. Handler Registration

// Register all handlers at once
let registry = HandlerRegistry::new();
registry.register_simple("increment", |m| /* ... */);
registry.register_simple("decrement", |m| /* ... */);
registry.register_with_value("update", |m, v| /* ... */);

3. XML Structure

<!-- Good: Clear hierarchy -->
<column spacing="20">
    <text value="Title" size="24" />
    <row spacing="10">
        <button label="OK" on_click="ok" />
        <button label="Cancel" on_click="cancel" />
    </row>
</column>

<!-- Avoid: Deep nesting without layout -->
<container>
    <container>
        <container>
            <text value="Too deep!" />
        </container>
    </container>
</container>

Troubleshooting

Builder doesn't update on changes

  • Ensure you're using hot-reload mode
  • Check file permissions
  • Verify XML is valid

Bindings not evaluating

  • Check field names match exactly (case-sensitive)
  • Verify model implements UiBindable
  • Enable verbose mode to see evaluation errors

Events not firing

  • Verify handler is registered in HandlerRegistry
  • Check handler name matches XML attribute
  • Ensure handler is registered in HandlerRegistry

Contributing

See the main repository CONTRIBUTING.md for guidelines.

License

This project is licensed under either of:

  • Apache License, Version 2.0
  • MIT license

at your option.

Status

Current Version: 0.1.0 (MVP)
Phase: Phase 6 - Polish & Documentation
All Tests: ✅ Passing (28/28)
Examples: ✅ Working (hello-world, counter, todo-app, styling)