13 releases
Uses new Rust 2024
| 3.0.0 |
|
|---|---|
| 0.3.2 | Feb 5, 2026 |
| 0.3.0 | Jan 31, 2026 |
| 0.2.8 | Jan 24, 2026 |
| 0.1.0 | Jan 9, 2026 |
#115 in GUI
Used in 2 crates
1MB
21K
SLoC
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
- ✅ State-Aware Styling: Hover, focus, active, and disabled states for interactive widgets
- ✅ Compile-Time Logging: Debug output only in debug builds (no runtime overhead)
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
// 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
- Centralized Interpretation: All widget rendering logic lives in the builder
- Type Safety: No runtime type erasure, compile-time verified
- Backend Agnostic: Core logic separate from Iced-specific widgets
- 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
- Reuse builders: Builder instances are single-use, but cheap to create
- Minimize bindings: Each binding adds ~700ns overhead
- Use style classes: Reuse styles instead of inline attributes
- Debug in debug builds: Verbose logging is automatically stripped from release builds
Error Handling
Debug Mode
Debug logging is automatically enabled in debug builds:
// Debug builds: see detailed logging
// Release builds: no logging overhead
let widget = DampenWidgetBuilder::new(&node, &model, Some(®istry))
.build();
Common Errors
- Missing handler: Event is silently ignored (debug build logs warning)
- Binding error: Returns empty string (debug build logs error)
- Invalid attribute: Uses default value (debug build logs warning)
- Unsupported widget: Returns empty placeholder (debug build 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 - Debug builds show evaluation errors automatically
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.2.7
Phase: Phase 9 - Complete (All refactoring complete)
All Tests: ✅ Passing
Examples: ✅ Working (hello-world, counter, todo-app, styling)
v0.2.7 Changelog
Added
- Helper functions for boolean attribute parsing (
resolve_boolean_attribute) - Handler resolution helper with rich error context (
resolve_handler_param) - Generic state-aware styling helper (
create_state_aware_style_fn) - State-aware styling support for slider widget
Changed
- Deprecated
IcedBackend(removal in v0.3.0) - Verbose logging now compile-time gated (no runtime overhead)
- Improved error messages with suggestions
Performance
- Reduced code duplication by ~370+ lines
- Rc-wrapped StyleClass for efficient cloning in closures
- No verbose logging overhead in release builds
Dependencies
~66–90MB
~1.5M SLoC