Skip to main content

iced_markup/
lib.rs

1//! # iced-markup
2//!
3//! A declarative markup DSL (Domain-Specific Language) for building
4//! [Iced](https://iced.rs) GUI applications in Rust.
5//!
6//! Instead of writing deeply nested builder chains by hand, you write a
7//! concise, JSX-inspired syntax inside the [`view!`] macro and get
8//! idiomatic Iced widget code at compile time — zero runtime overhead.
9//!
10//! ## Quick example
11//!
12//! ```no_run
13//! use iced::widget::{column, row, text, button};
14//! use iced_markup::view;
15//!
16//! #[derive(Clone)]
17//! enum Msg { Click }
18//!
19//! fn view() -> iced::Element<'static, Msg> {
20//!     view! {
21//!         column ![spacing: 20, padding: 40] {
22//!             text("Welcome to Iced!") {},
23//!             row ![spacing: 10] {
24//!                 button("Increment") ![on_press: Msg::Click] {},
25//!                 button("Decrement") ![on_press: Msg::Click] {},
26//!             },
27//!         }
28//!     }
29//!     .into()
30//! }
31//! ```
32//!
33//! ## Advanced Features
34//!
35//! `iced_markup` provides several advanced features to simplify complex UIs:
36//!
37//! ### 1. Native Control Flow
38//! Use real `if` and `for` blocks inside your markup:
39//!
40//! ```no_run
41//! # use iced_markup::view;
42//! # use iced::widget::{column, text, Column};
43//! # use iced::{Theme, Renderer};
44//! # let show_admin = true;
45//! # let items = vec!["A"];
46//! # enum Msg { Item }
47//! let _: Column<'_, Msg, Theme, Renderer> = view! {
48//!     column {
49//!         if show_admin {
50//!             text("Admin Panel") {}
51//!         },
52//!         for item in items {
53//!             text(item) {}
54//!         }
55//!     }
56//! };
57//! ```
58//!
59//! ### 2. Thematic Pipes (`|`)
60//! Apply styles or themes with a sleek syntax:
61//!
62//! ```no_run
63//! # use iced_markup::view;
64//! # use iced::widget::{text, Column, column};
65//! # use iced::{Theme, Renderer};
66//! # enum Msg { Item }
67//! let _: Column<'_, Msg, Theme, Renderer> = view! {
68//!     column {
69//!         text("Hello") | |_: &Theme| iced::widget::text::Style { color: Some(iced::Color::WHITE) } {}
70//!     }
71//! };
72//! ```
73//!
74//! ### 3. Event Shorthands (`+`)
75//! Use `+click`, `+input`, or `+submit` for common events:
76//!
77//! ```no_run
78//! # use iced_markup::view;
79//! # use iced::widget::{button, Column, column};
80//! # use iced::{Theme, Renderer};
81//! # #[derive(Clone)] enum Msg { Save }
82//! let _: Column<'_, Msg, Theme, Renderer> = view! {
83//!     column {
84//!         button("Save") ![+click: Msg::Save] {}
85//!     }
86//! };
87//! ```
88//!
89//! ### 4. Component Slots (`@`)
90//! Use named slots for flexible widget configuration:
91//!
92//! ```no_run
93//! # use iced_markup::view;
94//! # use iced::widget::{button, Column, column};
95//! # use iced::{Theme, Renderer};
96//! # #[derive(Clone)] enum Msg { Click }
97//! let _: Column<'_, Msg, Theme, Renderer> = view! {
98//!     column {
99//!         button("Submit") {
100//!             @on_press: Msg::Click
101//!         }
102//!     }
103//! };
104//! ```
105//!
106//! ## Architecture
107//!
108//! The crate is structured as a classic compiler pipeline:
109//!
110//! 1. **[`parser`]** — Reads the token stream and produces a typed AST.
111//! 2. **[`node`]** — Defines every node type (widgets, attributes, control flow).
112//! 3. **[`codegen`]** — Implements `ToTokens` for every AST node, emitting
113//!    optimized Iced Rust code.
114//!
115//! The public entry point is the [`view!`] procedural macro defined below.
116
117mod attribute;
118mod codegen;
119mod node;
120mod parser;
121
122use proc_macro::TokenStream;
123use proc_macro_error2::proc_macro_error;
124use quote::ToTokens;
125use syn::parse_macro_input;
126
127use crate::node::Markup;
128
129/// The `view!` procedural macro transforms a declarative, JSX-inspired markup syntax
130/// into idiomatic [Iced](https://iced.rs) widget code at compile time.
131///
132/// This provides a much more readable and maintainable way to define GUI layouts
133/// compared to deeply nested builder chains, while maintaining zero runtime overhead.
134///
135/// # Syntax breakdown
136///
137/// ```text
138/// view! {
139///     widget_name(constructor_args) ![attribute: value] {
140///         child_widget,
141///     }
142/// }
143/// ```
144///
145/// - **`widget_name`**: The Iced widget to create (e.g., `column`, `row`, `text`). Only widgets that support `.push()` (like `Column` and `Row`) should have children in `{}` blocks.
146/// - **`constructor_args`**: (Optional) Arguments passed to the widget's creation function.
147/// - **`attributes`**: (Optional) Modifiers applied via `![...]` (e.g., `![spacing: 10]`).
148/// - **`children`**: (Optional) Nested widgets inside `{...}` separated by commas.
149///
150/// # Example
151///
152/// ```no_run
153/// # use iced_markup::view;
154/// # use iced::widget::{column, text, button, Column};
155/// # use iced::{Theme, Renderer};
156/// # #[derive(Clone)] enum Message { Clicked }
157/// let _: Column<'_, Message, Theme, Renderer> = view! {
158///     column ![spacing: 20] {
159///         text("Hello World") {},
160///         button("Click me") ![on_press: Message::Clicked] {},
161///     }
162/// };
163/// ```
164///
165/// Expands roughly to:
166///
167/// ```no_run
168/// # use iced::widget::{column, text, button, Column};
169/// # use iced::{Theme, Renderer};
170/// # #[derive(Clone)] enum Message { Clicked }
171/// let _: Column<'_, Message, Theme, Renderer> = iced::widget::column([])
172///     .spacing(20)
173///     .push(iced::widget::text("Hello World"))
174///     .push(iced::widget::button("Click me").on_press(Message::Clicked));
175/// ```
176#[proc_macro]
177#[proc_macro_error]
178pub fn view(input: TokenStream) -> TokenStream {
179    // 1. Parse: Convert the input tokens into our AST structure.
180    let markup = parse_macro_input!(input as Markup);
181
182    // 2. Codegen: Transform the AST into valid Rust code.
183    let tokens = markup.to_token_stream();
184
185    // 3. Output: Return the generated code back to the compiler.
186    TokenStream::from(tokens)
187}