diff --git a/Cargo.lock b/Cargo.lock index 5413dea..a0fba5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2078,6 +2078,15 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + [[package]] name = "rustc-hash" version = "2.1.3" @@ -2187,6 +2196,17 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "rusty-xaml" +version = "0.1.0" +dependencies = [ + "roxmltree", + "rusty", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "ryu" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 7ee0431..9401a38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,14 @@ [workspace] -members = ["rusty", "rusty-filter", "rusty-macros", "rusty-ivyml", "rusty-server", "rusty-docs", "rusty-desktop"] +members = [ + "rusty", + "rusty-filter", + "rusty-macros", + "rusty-ivyml", + "rusty-xaml", + "rusty-server", + "rusty-docs", + "rusty-desktop", +] resolver = "2" [workspace.package] @@ -21,3 +30,4 @@ tower-http = { version = "0.6", features = ["fs"] } clap = { version = "4", features = ["derive", "env"] } tokio-tungstenite = "0.29" bytes = "1" +roxmltree = "0.21" diff --git a/README.md b/README.md index aa9880f..033a0cd 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Rusty-Framework follows the same architecture as Ivy-Framework: | `rusty-macros` | Proc macros for `#[derive(Widget)]`, `#[prop]`, `#[event]` | | `rusty-ivyml` | Proc macros for `ivyml!` / `ivyml_file!` declarative markup | | `rusty-filter` | Filter query lexer/parser/AST and evaluator | +| `rusty-xaml` | Parses XAML markup into widget trees at runtime | | `rusty-server` | Standalone server binary | | `rusty-docs` | Docs site binary; `build.rs` generates `src/generated/` | | `rusty-desktop` | Native shell via wry/tao behind the optional `shell` feature | diff --git a/rusty-xaml/Cargo.toml b/rusty-xaml/Cargo.toml new file mode 100644 index 0000000..4e8fed5 --- /dev/null +++ b/rusty-xaml/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "rusty-xaml" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Parses XAML UI documents into Rusty widget trees at runtime" + +[dependencies] +rusty = { path = "../rusty" } +roxmltree.workspace = true +# `value.rs` deserializes XAML enum words straight into `rusty`'s enums, which +# needs the `DeserializeOwned` bound that `serde_json` does not re-export. +serde.workspace = true +serde_json.workspace = true + +[dev-dependencies] +# For `examples/xaml_counter.rs`, which serves the parsed tree like the +# examples in `rusty/examples`. +tokio.workspace = true diff --git a/rusty-xaml/examples/xaml_counter.rs b/rusty-xaml/examples/xaml_counter.rs new file mode 100644 index 0000000..17fd58a --- /dev/null +++ b/rusty-xaml/examples/xaml_counter.rs @@ -0,0 +1,82 @@ +//! `rusty/examples/counter.rs`, with the UI in XAML instead of builder calls. +//! +//! Run it with `cargo run -p rusty-xaml --example xaml_counter`, then open +//! . +//! +//! The point of interest is where the parse happens: *inside* `build`, against a +//! context rebuilt from the current state. A binding is resolved once, when the +//! document is parsed, so parsing per build is what makes `{Binding Count}` track +//! the counter — see the crate docs. The markup itself could equally come from a +//! file with `parse_file_with`, which is the case this crate exists for. + +use rusty::prelude::*; +use rusty_xaml::XamlContext; + +/// Hand-written here to keep the example self-contained; a real app would load +/// this from disk, from a database, or from an editor. +const MARKUP: &str = r#" + + + + + +