windjammer-ui 0.3.6

Cross-platform UI framework for Windjammer (Web, Desktop, Mobile)
Documentation
// Reactive Todo App
// Demonstrates: Complex state management, lists, filtering, reactivity

use windjammer_ui.prelude.*
use windjammer_ui.vdom.{VElement, VNode, VText}
use windjammer_ui.reactivity.Signal

struct Todo {
    id: int,
    text: String,
    completed: bool,
}

@component
struct TodoApp {
    todos: Signal<Vec<Todo>>,
    input: Signal<String>,
    filter: Signal<String> // "all", "active", "completed",
    next_id: Signal<int>,
}

impl Component for TodoApp {
    fn render() -> VNode {
        let current_input = input.get()
        let filtered_todos = get_filtered_todos()
        let active_count = get_active_count()
        
        VElement.new("div")
            .attr("class", "todo-app")
            .child(VNode.Element(
                VElement.new("h1")
                    .child(VNode.Text(VText.new("Windjammer Todos")))
            ))
            .child(VNode.Element(
                VElement.new("input")
                    .attr("type", "text")
                    .attr("placeholder", "What needs to be done?")
                    .attr("value", current_input)
                    .attr("oninput", "update_input")
            ))
            .child(render_todo_list(filtered_todos))
            .child(VNode.Text(VText.new("{active_count} items left")))
            .into()
    }
}

impl TodoApp {
    fn new() -> Self {
        Self {
            todos: Signal.new(Vec.new()),
            input: Signal.new(String.new()),
            filter: Signal.new("all".to_string()),
            next_id: Signal.new(0),
        }
    }
    
    fn render_todo_list(todos: Vec<Todo>) -> VNode {
        let items = todos.iter().map(|todo| {
            VElement.new("li")
                .child(VNode.Text(VText.new(todo.text.clone())))
                .into()
        }).collect()
        
        VElement.new("ul").children(items).into()
    }
    
    fn add_todo() {
        let text = input.get()
        if text.len() > 0 {
            let id = next_id.get()
            todos.update(|list| {
                list.push(Todo { id, text: text.clone(), completed: false })
            })
            next_id.update(|n| *n += 1)
            input.set(String.new())
        }
    }
    
    fn get_filtered_todos() -> Vec<Todo> {
        todos.get()
    }
    
    fn get_active_count() -> int {
        todos.get().iter().filter(|t| !t.completed).count()
    }
    
    fn update_input(value: String) {
        input.set(value)
    }
}

fn main() {
    let app = TodoApp.new()
    mount("#app", app)
}